当前位置: 首页 > ops >正文

Java对象创建过程

前言

在Java开发中,我们经常使用new关键字来创建对象,但你是否想过,当执行Person person = new Person()这行代码时,JVM底层究竟发生了什么?让我们看看对象是怎么被创建的。

对象创建的六个核心步骤

1. 类加载检查

当JVM执行引擎遇到new指令时,首先会进行类加载检查:

Person person = new Person();

JVM会执行以下检查:

  • 在常量池中定位到Person类的符号引用
  • 检查Person类是否已经被加载
  • 检查Person类是否已经被解析
  • 检查Person类是否已经被初始化

如果任何一个步骤没有完成,JVM会先执行相应的类加载过程。

2. 分配内存空间

类加载检查通过后,JVM开始为新对象分配内存。对象所需的内存大小在类加载完成后就已经确定。

2.1 两种分配方式

方式一:指针碰撞

定义:假设Java堆中内存是绝对规整的,所有使用过的内存放在一边,空闲的内存放在另一边,中间放着一个指针为分界点的指示器,分配内存就是把那个指针向空闲空间方向挪动一段与对象大小相等的距离。

方式二:空闲列表

定义:假设Java堆中内存并不是规整的,已被使用的内存和空闲的内存相互交错,虚拟机就必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

2.2 选择依据

if (垃圾收集器支持压缩整理) {使用指针碰撞方式();
} else {使用空闲列表方式();
}

3. 并发安全处理

对象创建是高频操作,必须保证线程安全。JVM提供两种解决方案:

3.1 CAS + 失败重试

public Object allocateMemory(int size) {do {Object current = heapPointer.get();Object next = current + size;if (heapPointer.compareAndSet(current, next)) {return current;}// CAS失败,重试} while (true);
}

3.2 TLAB

每个线程在Java堆中预先分配一块私有内存区域,避免多线程竞争。

TLAB的优势:

  • 减少线程间同步开销
  • 提高内存分配效率
  • 支持快速的对象分配

4. 内存初始化

public class Person {private String name;    // 初始化为nullprivate int age;        // 初始化为0private boolean active; // 初始化为false
}

JVM将分配的内存空间(除对象头外)全部初始化为零值,这确保了实例字段在未显式赋值时也有确定的初始值。

各类型的零值:

数据类型零值
booleanfalse
byte(byte)0
short(short)0
int0
long0L
float0.0f
double0.0d
char'\u0000'
引用类型null

5. 设置对象头

对象头是JVM管理对象的关键数据结构,包含两部分:

5.1 Mark Word

在64位JVM中,Mark Word占用8字节,存储: 对象哈希码、分代年龄、锁标志位、线程ID、时间戳和偏向锁标志。

Mark Word在不同锁状态下的存储内容:

锁状态25bit31bit1bit4bit1bit2bit
无锁状态unusedhashcodeunused分代年龄001
偏向锁ThreadID(54bit)Epoch(2bit)unused分代年龄101
轻量级锁指向栈中锁记录的指针(62bit)unused分代年龄unused00
重量级锁指向互斥量的指针(62bit)unused分代年龄unused10
GC标记CMS过程用的标记信息(62bit)unused分代年龄unused11

5.2 类型指针

类型指针指向对象的类元数据,JVM通过这个指针确定对象是哪个类的实例。在64位JVM中,类型指针通常占用8字节,但开启压缩指针后可压缩至4字节。

5.3 对象头示例

普通对象的对象头结构:

public class Person {private String name;private int age;
}

对于上述Person对象,其对象头包含:

组成部分大小(64位JVM)内容描述
Mark Word8字节哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
类型指针4字节(压缩)/8字节指向Person.class的类元数据信息
对齐填充0-7字节确保对象大小为8字节的倍数

数组对象的对象头结构:        

数组对象除了Mark Word和类型指针外,还有额外的4字节存储数组长度:

int[] array = new int[10];
组成部分大小(64位JVM)内容描述
Mark Word8字节对象标记信息
类型指针4字节(压缩)/8字节指向int[]的类元数据
数组长度4字节存储数组的长度(10)
对齐填充根据需要保证对象大小对齐

6. 执行构造函数

public class Person {private String name;private int age;// 编译器生成的<init>方法public Person(String name, int age) {super();           // 调用父类构造器this.name = name;  // 实例字段初始化this.age = age;    // 实例字段初始化// 构造函数体逻辑}
}

构造函数执行的详细过程:

  1. 隐式调用父类构造器:如果没有显式调用super(),编译器会自动添加super()调用
  2. 实例字段初始化:按照在类中声明的顺序执行字段初始化
  3. 执行构造函数体:执行构造函数中的自定义逻辑

 

http://www.xdnf.cn/news/11933.html

相关文章:

  • 操作系统入门:核心概念与设计逻辑
  • 数字孪生在智能制造中的实践:某汽车总装车间的全流程仿真优化
  • https和http有什么区别-http各个版本有什么区别
  • DINO-R1
  • 商务合同范本智能审核系统 AI 大模型处理方案
  • 探索分布式存储与通信:去中心化共享及通訊(DSAC)
  • 区块链跨链通信:使用 Cosmos SDK 实现链间互操作
  • 手动清理C盘文件的一些方法
  • 共聚焦显微镜—赋能光学元件精密质控
  • C语言获取数组长度方法大全(附带实例)
  • gateway 网关 路由新增 (已亲测)
  • Python训练营打卡 Day44
  • linux shell脚本硬件定时检测通过邮箱警告管理人员
  • LLM之RAG实战(五十四)| 复杂文档处理RAG框架:Ragflow
  • 振动力学:弹性杆的纵向振动(固有振动和固有频率的概念)
  • [蓝桥杯]填字母游戏
  • 短视频矩阵系统源码新发布技术方案有那几种?
  • 2025.6.4总结
  • 霍夫曼编码详解
  • qiankun模式下 主应用严格模式,子应用el-popover 点击无效不显示
  • STM32L0看门狗设置LL库
  • ABP-Book Store Application中文讲解 - Part 9: Authors: User Interface
  • 代码随想录刷题day29
  • 【免费】酒店布草洗涤厂自动统计管理系统(1)——智能编程——仙盟创梦IDE
  • Redis中的过期策略与内存淘汰策略
  • 剩余类和完全剩余系
  • 【Linux】Linux程序地址基础
  • ‘utf-8‘ codec can‘t decode byte 0xc9 in position 18:
  • css-塞贝尔曲线
  • Ubuntu 25.10 将默认使用 sudo-rs