new 触发类加载(含初始化)后分配内存并写入对象头,再自顶向下执行构造器链;反射、clone、反序列化等绕过构造器,导致字段未按预期初始化。

new 关键字触发类加载与构造调用
Java 中最常用的对象创建方式是 new,但它背后不是简单分配内存。当执行 new MyClass() 时,JVM 首先检查 MyClass 是否已加载:若未加载,会触发类加载机制(加载 → 验证 → 准备 → 解析 → 初始化);其中「初始化」阶段会执行 static 块和静态字段赋值。只有类初始化完成,才会进入实例化流程。
注意:类加载是线程安全的,但仅保证「一个类只被初始化一次」;多个线程同时触发首次 new,只有一个能执行 方法,其余阻塞等待。
- 如果类加载失败(如
NoClassDefFoundError或ClassNotFoundException),new不会执行,直接抛异常 -
final static字段在准备阶段就赋默认值,但显式初始化(如public static final int X = calc();)一定发生在初始化阶段 - 父类总在子类之前完成初始化,哪怕子类
new操作中没显式引用父类
内存分配与对象头写入发生在构造函数执行前
类初始化完成后,JVM 在堆上为对象分配内存(具体策略取决于 GC 算法,如 TLAB 分配),并立即写入对象头(Object Header),包括 Mark Word(存储哈希码、GC 分代年龄、锁状态等)和 Class Pointer(指向 MyClass.class 的元数据)。
此时对象已是一个“可识别的 Java 对象”,哪怕构造函数还没开始执行——这也是为什么在构造器中泄露 this(如启动线程、注册监听器)会导致其他线程看到未初始化完成的对象状态。
立即学习“Java免费学习笔记(深入)”;
- 分配失败会抛
OutOfMemoryError: Java heap space,与构造逻辑无关 - 对象头中的 Class Pointer 是运行时确定的,支持多态;它不等于编译期类型,而是实际 new 的那个类
- 数组对象略有不同:其对象头还包含长度字段,且类元数据是 JVM 运行时动态生成的(如
[Ljava.lang.String;)
构造器链执行顺序:从 Object 到当前类
内存分配和对象头写入完成后,JVM 才真正调用构造器。Java 强制要求每个构造器第一行必须是 this(...) 或 super(...)(隐式或显式)。因此构造调用是自顶向下的:先执行 Object(),再逐级向下,最后执行当前类构造体中的代码。
这意味着字段初始化(包括实例字段赋值、实例初始化块)是在对应类的构造器体中「插入」执行的,而非在分配后统一处理。
class A {
String a = initA(); // 在 A 的构造器体开头插入
{ System.out.println("A init block"); } // 同样插入
A() { System.out.println("A ctor"); }
}
class B extends A {
String b = initB(); // 在 B 的构造器体开头插入
{ System.out.println("B init block"); }
B() { System.out.println("B ctor"); }
}
执行 new B() 输出顺序是:A init block → A ctor → B init block → B ctor。字段初始化永远绑定在所属类的构造流程中,不会提前。
反射、clone、反序列化等非 new 创建方式绕过部分步骤
Class.newInstance()(已弃用)、Constructor.newInstance()、Object.clone()、反序列化(ObjectInputStream.readObject())也能创建对象,但流程不同:
-
Constructor.newInstance():仍触发类加载和初始化,但跳过「检查访问权限」以外的编译期校验;它通过 JNI 调用底层构造逻辑,本质仍是构造器执行 -
clone():不调用任何构造器,直接复制堆上内存(浅拷贝),对象头中 Class Pointer 保持不变;要求类实现Cloneable,否则抛CloneNotSupportedException - 反序列化:不执行构造器、不走字段初始化块,由 JVM 直接填充字段值(包括 private 和 final 字段),然后调用
readObject()方法(如果定义了)
这些方式容易造成对象状态不一致,比如 clone() 后 final 字段看似被修改(实为绕过语义检查),或反序列化出未经过构造器校验的对象。
真正关键的分水岭不在「怎么写 new」,而在于是否触发构造器执行——这决定了字段是否按预期初始化、资源是否被申请、不变量是否被建立。很多 NPE 或状态错乱,根源是误以为 clone 或反序列化也会走构造逻辑。










