对象创建流程
Step1:类加载检查 —— 确保对象的 “模板” 可用
当 JVM 执行new
指令时,首先通过指令参数在常量池中定位到目标类的符号引用(如类的全限定名),然后检查该类是否已完成加载、解析、初始化(即类加载的全过程)。
- 若未加载:触发类加载流程(加载→验证→准备→解析→初始化),将类的字节码加载到方法区,生成 Class 对象,确保对象创建时有可遵循的类元数据(如字段、方法定义)。
- 若已加载:直接进入下一步,避免重复加载浪费资源。
Step2:分配内存 —— 为对象划分堆空间
类加载完成后,JVM 可通过类元数据确定对象所需的内存大小(固定值,由字段数量和类型决定),随后从 Java 堆中划分出对应大小的内存块。
(1)两种内存分配方式(取决于堆内存是否规整)
指针碰撞:
- 适用场景:堆内存连续(无碎片),如采用 “标记 - 整理”“复制算法” 的 GC 收集器(Serial、ParNew)。
- 原理:堆内存分为 “已使用” 和 “未使用” 两部分,中间用一个指针分隔。分配时只需将指针向未使用区域移动 “对象大小” 的距离,操作高效。
空闲列表:
- 适用场景:堆内存不规整(有碎片),如采用 “标记 - 清除” 算法的 GC 收集器(CMS)。
- 原理:JVM 维护一张记录所有可用内存块的列表,分配时从列表中找到一块足够大的内存块划分给对象,同时更新列表(移除已分配块,若有剩余则拆分后重新加入)。
(2)解决并发分配冲突(线程安全保障)
多线程同时创建对象时,可能出现 “同一块内存被多次分配” 的问题,JVM 通过两种机制解决:
- CAS + 失败重试:基于乐观锁思想,分配时通过 CAS 操作原子性地更新指针或内存块状态,若冲突则重试,直到成功。
- TLAB(线程本地分配缓冲区):为每个线程在 Eden 区预先分配一块私有内存,线程优先在自己的 TLAB 中分配对象,仅当 TLAB 不足时才使用 CAS 竞争公共内存,减少并发冲突。
Step3:初始化零值 —— 保证字段的默认可用性
内存分配完成后,JVM 会将分配到的内存空间(除对象头外的实例字段区域)全部初始化为零值(如 int 为 0、boolean 为 false、引用类型为 null)。
- 作用:确保对象在 Java 代码中未显式赋值的字段也能被安全访问(直接使用默认零值),避免未初始化导致的不可预知错误。
Step4:设置对象头 —— 标记对象的 “身份信息”
零值初始化后,JVM 需在对象的对象头中存储关键元信息,包括:
- 类元数据指针:指向方法区中该对象所属类的 Class 实例,用于确定对象的类型(如 “这个对象是 User 类的实例”)。
- 对象哈希码:对象的唯一标识(延迟计算,首次调用
hashCode()
时生成)。 - GC 分代年龄:记录对象经历 GC 的次数,用于判断是否晋升到老年代。
- 锁状态标志:如偏向锁、轻量级锁、重量级锁的状态标记,用于同步控制。这些信息是 JVM 识别对象、管理内存、实现同步的基础。
Step5:执行<init>方法 —— 完成对象的业务初始化
从 JVM 视角,经过前 4 步,一个 “空对象” 已创建,但从 Java 程序视角,对象尚未按业务逻辑初始化(字段仍为零值)。因此,new
指令后会立即执行 **<init>方法 **(即类的构造方法,由编译器根据代码生成)。
- 作用:按程序员定义的逻辑初始化对象(如给字段赋值、调用父类构造方法等),最终生成一个 “可用” 的对象。
总结:Java 对象的创建过程从 “类的合法性校验” 到 “内存分配与安全控制”,再到 “底层初始化” 和 “业务初始化”,完整覆盖了从字节码指令到可用实例的全流程,既保证了 JVM 运行时的规范性,也满足了业务逻辑对对象状态的要求