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

4 阅读4分钟

Java 对象创建过程,一般按 “类加载检查 → 分配内存 → 初始化零值 → 设置对象头 → 执行 <init> 这条线来回答。


一、对象创建的完整过程

1. 类加载检查

执行 new 指令时,JVM 会先检查这个类是否已经被加载、解析和初始化。

如果没有,就先执行类加载过程:

  • 加载
  • 验证
  • 准备
  • 解析
  • 初始化

也就是说,对象能不能创建,前提是类已经可用


2. 为对象分配内存

类加载完成后,JVM 已经知道这个对象需要多大内存。

然后在堆中给对象分配空间。分配方式常见有两种:

  • 指针碰撞

    • 适用于内存规整的场景
    • 用过的内存放一边,空闲内存放另一边,中间用指针分隔
    • 分配时只需要移动指针
  • 空闲列表

    • 适用于内存不规整的场景
    • JVM 维护一张空闲内存表,分配时从表中找到足够大的空间

是否规整,通常和 GC 使用的收集器是否带压缩整理 有关。


3. 处理并发安全问题

因为对象创建在多线程下可能同时发生,所以分配内存时要保证线程安全,常见方式:

  • CAS + 重试

  • TLAB(Thread Local Allocation Buffer)

    • 给每个线程预先分配一小块私有缓冲区
    • 线程优先在自己的 TLAB 中分配对象
    • 减少锁竞争,提高分配效率

面试里提到 TLAB,通常会加分。


4. 初始化为零值

内存分配完成后,JVM 会先把对象实例数据区域初始化为零值:

  • int -> 0
  • long -> 0L
  • boolean -> false
  • 引用类型 -> null

这一步保证对象字段即使不显式赋值,也有默认值。

注意:
这时还 不是程序员代码里写的初始化值,只是 JVM 默认零值。


5. 设置对象头

接下来 JVM 会设置对象头中的信息,例如:

  • 这个对象属于哪个类
  • 对象的哈希码
  • GC 分代年龄
  • 锁状态标志
  • 偏向锁线程 ID(特定场景下)

如果是数组,对象头里还会记录数组长度。


6. 执行 <init> 方法

最后执行构造方法,也就是字节码层面的 <init>

这一阶段才真正按代码逻辑进行初始化,比如:

User user = new User();

如果 User 类中写了:

private int age = 18;
public User() {
    this.name = "Tom";
}

那么这些赋值操作都是在 <init> 中完成的。

这一步执行完,才算一个真正可用的对象创建完成。


二、可以概括成一句话

对象创建过程就是:先检查类是否已加载,然后在堆上分配内存,接着把实例字段设为默认零值,再设置对象头,最后执行构造方法 <init> 完成程序员定义的初始化。


三、面试高频补充点

1. 对象一定在堆上吗?

通常是,但不绝对。

在 JIT 优化下,可能发生:

  • 逃逸分析
  • 标量替换
  • 栈上分配

所以有些对象未必真的落到堆里。


2. new 关键字做了什么?

可以回答:

  • 找到类元信息
  • 申请对象内存
  • 默认值初始化
  • 调用构造方法
  • 返回对象引用

3. 成员变量初始化顺序

一个对象初始化时,通常顺序可理解为:

  • 父类静态变量 / 静态代码块(类加载时,只执行一次)
  • 子类静态变量 / 静态代码块
  • 父类实例变量 / 实例代码块
  • 父类构造方法
  • 子类实例变量 / 实例代码块
  • 子类构造方法

四、背诵版

Java 对象创建时,首先会检查对应的类是否已经完成加载、解析和初始化;如果没有,会先进行类加载。然后 JVM 在堆中为对象分配内存,分配时会通过 CAS 或 TLAB 保证线程安全。分配完成后,会先把对象实例字段初始化为零值,再设置对象头信息,例如类指针、哈希码、GC 年龄和锁标志等。最后执行构造方法 <init>,按照程序员写的逻辑完成成员变量赋值和对象初始化,这样一个对象才算真正创建完成。


五、简版口诀

类加载检查,分配内存,零值初始化,设置对象头,执行构造方法。