对象的创建过程是怎样的?

2025年 阅读约 8 分钟 面试指南 · Java面试 · JVM

深入解析Java对象创建全过程:类加载检查、内存分配(指针碰撞/空闲列表)、初始化零值、设置对象头、执行init方法,附TLAB分配机制、对象内存布局和面试模拟。

一句话总结

Java 对象创建分五步:① 类加载检查(检查类是否已加载)→ ② 分配内存(指针碰撞或空闲列表,通过 CAS+失败重试保证线程安全,或使用 TLAB 减少竞争)→ ③ 初始化零值(将分配的内存空间初始化为零值)→ ④ 设置对象头(Mark Word + 类型指针)→ ⑤ 执行 <init>(构造函数,按父类到子类的顺序执行)。

初级理解

对象创建五步流程

// new Object() 的完整过程 Object obj = new Object(); // ① 类加载检查 // 检查 Object 类是否已加载,未加载则先执行类加载 // ② 分配内存 // 在堆中为新对象分配一块内存空间 // 分配方式:指针碰撞(规整堆)或 空闲列表(碎片堆) // ③ 初始化零值 // 将分配的内存空间初始化为零值(不包括对象头) // int → 0, boolean → false, 引用 → null // ④ 设置对象头 // 设置 Mark Word(哈希码、GC 分代年龄、锁状态等) // 设置类型指针(指向方法区中的类元数据) // ⑤ 执行 <init> // 执行构造函数,包括实例变量初始化、实例代码块、构造方法 // 按父类 → 子类的顺序执行

对象内存布局

// 一个 Java 对象在堆中的内存布局(64位 JVM,压缩指针开启) ┌──────────────────────────────────────┐ │ 对象头 Header │ ├──────────────────────────────────────┤ │ Mark Word(8 字节) │ │ - 哈希码(25 bit) │ │ - GC 分代年龄(4 bit) │ │ - 偏向锁标记(1 bit) │ │ - 锁标志位(2 bit) │ ├──────────────────────────────────────┤ │ 类型指针 Klass Pointer(4 字节压缩) │ │ 指向方法区中的 Class 元数据 │ ├──────────────────────────────────────┤ │ 数组长度(如果是数组,4 字节) │ ├──────────────────────────────────────┤ │ 实例数据 Instance Data │ │ 按字段类型和长度排列 │ │ (相同宽度的字段分配在一起) │ ├──────────────────────────────────────┤ │ 对齐填充 Padding │ │ 保证对象大小是 8 字节的整数倍 │ └──────────────────────────────────────┘

中级深入

内存分配的线程安全

// 问题:多个线程同时 new 对象,如何保证内存分配安全? // 方案一:CAS + 失败重试 // 使用 CAS 操作更新内存分配指针,失败则重试 // 简单但竞争激烈时效率低 // 方案二:TLAB(Thread Local Allocation Buffer) // 每个线程在 Eden 区预分配一块私有缓冲区(默认 Eden 的 1%) // 线程在自己的 TLAB 中分配对象,无需同步 // TLAB 用完才需要申请新的 TLAB(需要同步) // -XX:+UseTLAB(默认开启) // -XX:TLABSize 设置 TLAB 大小

两种内存分配方式

方式适用场景原理
指针碰撞堆内存规整(Serial、ParNew)已用内存和空闲内存中间有一个指针,分配时指针移动对象大小距离
空闲列表堆内存不规整(CMS)维护一个空闲内存列表,分配时从列表中找到足够大的空间

对象访问定位

// ① 句柄访问(间接访问) // 栈 → 句柄池 → 对象实例数据 + 对象类型数据 // 优点:对象移动时只需改句柄,引用稳定 // 缺点:多一次间接访问 // ② 直接指针访问(HotSpot 使用) // 栈 → 对象实例数据(含类型指针)→ 对象类型数据 // 优点:访问速度快,少一次指针定位 // 缺点:对象移动时需要更新所有引用

高级拓展

压缩指针(Compressed Oops)

// 64 位 JVM 中,对象指针默认 8 字节 // 开启压缩指针后,指针压缩为 4 字节(堆 < 32GB) // -XX:+UseCompressedOops(默认开启) // -XX:+UseCompressedClassPointers(压缩类型指针) // 原理:Java 对象 8 字节对齐,地址低 3 位始终为 0 // 存储时右移 3 位,使用时左移 3 位 // 4 字节 × 8 = 32GB 寻址范围 // 好处:减少内存占用,提高缓存命中率

字段重排序

HotSpot 会对实例字段进行重排序以减少内存浪费:相同宽度的字段分配在一起,按 double/long → int/float → short/char → byte/boolean → 引用的顺序排列。父类字段在子类字段之前。

实战场景

查看对象内存布局

// 使用 JOL(Java Object Layout)查看对象布局 // 依赖:org.openjdk.jol:jol-core import org.openjdk.jol.info.ClassLayout; public class ObjectLayout { public static void main(String[] args) { Object obj = new Object(); System.out.println(ClassLayout.parseInstance(obj).toPrintable()); } } // 输出示例(64位,压缩指针开启): // OFFSET SIZE TYPE DESCRIPTION // 0 4 (object header) // Mark Word 前 4 字节 // 4 4 (object header) // Mark Word 后 4 字节 // 8 4 (object header) // 类型指针(压缩后 4 字节) // 12 4 (loss due to alignment) // 对齐填充 // Instance size: 16 bytes

面试模拟

Q:Java 对象创建的过程是怎样的?

A:五步:类加载检查分配内存(指针碰撞/空闲列表,TLAB 保证线程安全)→初始化零值设置对象头(Mark Word + 类型指针)→执行 <init>(构造函数,父类→子类)。

Q:对象在内存中的存储布局是怎样的?

A:对象头(Mark Word 8字节 + 类型指针 4/8字节 + 数组长度)→ 实例数据(字段按宽度排列,相同宽度放一起)→ 对齐填充(保证 8 字节对齐)。64 位 JVM 开启压缩指针后,普通对象头 12 字节,加 4 字节对齐填充共 16 字节。

Q:TLAB 是什么?

A:TLAB(Thread Local Allocation Buffer)是每个线程在 Eden 区预分配的私有缓冲区。线程在自己的 TLAB 中分配对象无需同步,减少 CAS 竞争。TLAB 用完才需要申请新的(需要同步)。默认开启,大小约为 Eden 的 1%。