Java作为典型的面向对象编程语言,对象创建是日常开发中最基础的操作之一,但很多人只清楚通过new关键字可以创建对象,并不了解其背后完整的执行流程。理解对象创建和初始化的步骤,能帮助我们更好地排查空指针、初始化顺序异常等问题。

Java对象创建的完整步骤
当我们执行new Object()这类语句时,JVM会按照以下顺序执行操作:
1. 类加载检查
JVM首先会检查这个对象的类是否已经被加载、解析和初始化过。如果没有,会先执行类加载过程,将类的字节码加载到方法区,完成类的验证、准备、解析、初始化等步骤。
2. 分配内存
类加载完成后,JVM会在堆内存中为对象分配空间。内存大小在类加载完成后就已经确定,分配方式分为指针碰撞和空闲列表两种,具体取决于堆内存是否规整。
3. 初始化零值
内存分配完成后,JVM会将分配到的内存空间都初始化为零值,这一步操作保证了对象的实例字段在不显式赋值的情况下也能直接使用对应的默认值,比如int类型默认是0,引用类型默认是null。
4. 设置对象头
接下来JVM会设置对象的对象头信息,包括对象的哈希码、GC分代年龄、锁状态标志、指向类元数据的指针等,这些信息是JVM管理对象的重要依据。
5. 执行构造方法
最后JVM会执行对象的构造方法,按照代码逻辑对对象的实例字段进行显式赋值,执行构造方法中的其他初始化逻辑,完成对象的自定义初始化。
OOP场景下的对象初始化顺序
在面向对象的继承场景中,对象初始化的顺序会遵循父类优先的原则,具体流程如下:
- 先执行父类的类初始化(静态变量、静态代码块),再执行子类的类初始化
- 父类实例变量初始化、父类非静态代码块,然后执行父类构造方法
- 子类实例变量初始化、子类非静态代码块,最后执行子类构造方法
我们可以通过一段代码来验证这个顺序:
// 父类
class Parent {
// 父类静态变量
private static int parentStaticVar = 1;
// 父类静态代码块
static {
System.out.println("父类静态代码块执行,parentStaticVar=" + parentStaticVar);
}
// 父类实例变量
private int parentInstanceVar = 2;
// 父类非静态代码块
{
System.out.println("父类非静态代码块执行,parentInstanceVar=" + parentInstanceVar);
}
// 父类构造方法
public Parent() {
System.out.println("父类构造方法执行");
}
}
// 子类
class Child extends Parent {
// 子类静态变量
private static int childStaticVar = 3;
// 子类静态代码块
static {
System.out.println("子类静态代码块执行,childStaticVar=" + childStaticVar);
}
// 子类实例变量
private int childInstanceVar = 4;
// 子类非静态代码块
{
System.out.println("子类非静态代码块执行,childInstanceVar=" + childInstanceVar);
}
// 子类构造方法
public Child() {
System.out.println("子类构造方法执行");
}
}
// 测试类
public class ObjectInitTest {
public static void main(String[] args) {
System.out.println("开始创建子类对象");
new Child();
}
}
执行上述代码后,输出结果如下:
开始创建子类对象 父类静态代码块执行,parentStaticVar=1 子类静态代码块执行,childStaticVar=3 父类非静态代码块执行,parentInstanceVar=2 父类构造方法执行 子类非静态代码块执行,childInstanceVar=4 子类构造方法执行
这个结果完全符合我们前面提到的OOP对象初始化顺序,也验证了对象创建过程中各个步骤的执行逻辑。
常见注意事项
在实际开发中,我们需要注意以下几点:
- 静态代码块和静态变量只会在类第一次加载时执行一次,后续创建对象不会再执行
- 如果父类构造方法中调用了可被子类重写的方法,可能会导致子类还没初始化完成就调用了子类的方法,引发逻辑异常
- 实例变量的显式赋值和非静态代码块的执行顺序和代码书写顺序一致,最终都会在构造方法执行前完成
理解对象创建和初始化的完整流程,不仅能帮助我们写出更健壮的代码,也能在遇到相关异常时快速定位问题根源。