欢迎光临
我们一直在努力

JVM内存机制下对象创建的全流程分析

目录

一、前置基础:JVM核心内存区域(对象创建相关)

二、对象创建全流程(核心重点)

先看示例代码(覆盖所有核心元素)

阶段1:类加载阶段(仅执行1次,第一次主动使用类时触发)

1.1 加载(Load)

1.2 验证(Verify)

1.3 准备(Prepare)

1.4 解析(Resolve)

1.5 初始化(Initialize)(核心步骤)

阶段2:对象实例化阶段(每次new都执行,类加载完成后)

2.1 虚拟机栈操作

2.2 堆内存分配

2.3 父类实例初始化(继承场景必执行)

2.4 子类实例属性初始化

2.5 子类构造代码块执行

2.6 子类构造方法执行

2.7 引用赋值

2.8 多次创建对象(如child2 = new Child())

三、内存可视化(直观理解)

3.1 类加载完成后内存状态

3.2 两个Child对象创建后内存状态​编辑

四、核心规则总结(必记)

4.1 静态资源(方法区)规则

4.2 实例资源(堆+栈)规则

4.3 构造代码块的本质

五、常见易错点(避坑重点)

六、最终运行结果(验证流程)

七、学习小结


核心目标:理解JVM内存模型与对象创建的关联,掌握静态属性、静态代码块、构造代码块、构造方法的执行顺序及内存变化,吃透面向对象底层逻辑。

一、前置基础:JVM核心内存区域(对象创建相关)

对象创建的全流程,本质是JVM各内存区域协同工作的过程,重点关注以下4个核心区域,无需深入底层细节,聚焦“对象/数据存储位置”即可。

内存区域核心作用生命周期关键存储内容(对象创建相关)
方法区(元空间) 存储类的元数据,全局共享 类加载时创建,JVM停止时销毁 类字节码、静态属性、静态代码块、常量池、类方法表
堆(Heap) 存储所有对象的实例数据 对象创建时分配,GC回收时释放 对象头、实例属性(成员变量)、对象的实际数据
虚拟机栈 存储方法调用栈帧,线程私有 方法执行时创建,方法结束时销毁 局部变量、对象引用(指向堆中对象)、方法返回地址
程序计数器 记录当前线程执行的字节码指令地址 线程私有,随线程生命周期 辅助方法执行,无直接关联对象创建的存储内容

二、对象创建全流程(核心重点)

对象创建分为两大核心阶段:类加载阶段(仅执行1次) 和 对象实例化阶段(每次new都执行),结合继承场景(父类+子类)完整拆解。

先看示例代码(覆盖所有核心元素)

// 父类:验证继承场景
class Parent {
   // 父类静态属性
   private static String parentStaticField = "父类静态属性";
   // 父类静态代码块
   static {
       System.out.println("1. 父类静态代码块执行:" + parentStaticField);
  }
   // 父类构造代码块
  {
       System.out.println("4. 父类构造代码块执行");
  }
   // 父类构造方法
   public Parent() {
       System.out.println("5. 父类构造方法执行");
  }
}

// 子类:包含所有核心元素(静态/实例、代码块/构造)
class Child extends Parent {
   // 子类静态属性(引用类型)
   private static Student staticStudent = new Student("静态学生");
   // 子类静态代码块
   static {
       System.out.println("2. 子类静态代码块执行:" + staticStudent.name);
  }
   // 子类实例属性
   private Student instanceStudent = new Student("实例学生");
   // 子类构造代码块
  {
       System.out.println("7. 子类构造代码块执行:" + instanceStudent.name);
  }
   // 子类构造方法
   public Child() {
       System.out.println("8. 子类构造方法执行");
  }
}

// 辅助类:观察对象创建
class Student {
   String name;
   public Student(String name) {
       this.name = name;
       System.out.println("创建Student对象:" + name);
  }
}

// 测试类
public class Test {
   public static void main(String[] args) {
       Child child1 = new Child(); // 第一次创建对象(触发类加载)
       Child child2 = new Child(); // 第二次创建对象(跳过类加载)
  }
}

阶段1:类加载阶段(仅执行1次,第一次主动使用类时触发)

触发条件:JVM第一次主动使用类(如new对象、访问静态属性、调用静态方法、反射等),子类加载会递归触发父类加载。

类加载分为5个步骤,重点关注「准备阶段」和「初始化阶段」(与静态资源相关):

1.1 加载(Load)

  • 操作:JVM读取类的字节码文件(.class),在方法区生成该类的元数据(类结构、方法表、字段表等)。

  • 内存变化:方法区新增父类(Parent)、子类(Child)、辅助类(Student)的类元数据,此时未初始化任何属性。

1.2 验证(Verify)

  • 操作:校验类的字节码合法性(如是否符合JVM规范、有无安全漏洞),确保类能正常运行。

  • 内存变化:无直接内存分配,仅做校验。

1.3 准备(Prepare)

  • 操作:为类的静态属性分配内存,并设置默认值(引用类型默认null,基本类型默认0/false等)。

  • 内存变化:

    • 方法区中,Parent.parentStaticField 分配内存,默认值 null;

    • 方法区中,Child.staticStudent 分配内存,默认值 null;

    • 注意:此时仅分配内存、设默认值,不执行静态属性的赋值语句(如new Student())。

1.4 解析(Resolve)

  • 操作:将类中的符号引用(如Student类名)转为JVM能识别的直接引用(内存地址),可选阶段(可延迟到初始化后执行)。

  • 内存变化:无明显内存分配,仅完善类元数据的引用关系。

1.5 初始化(Initialize)(核心步骤)

  • 操作:执行静态属性的赋值语句 + 静态代码块,按代码书写顺序执行;继承场景下,先执行父类静态,再执行子类静态。

  • 内存变化(结合示例):

  • 父类初始化:

    • 执行 parentStaticField = "父类静态属性",方法区中该字段赋值为常量池中的字符串;

    • 执行父类静态代码块,打印「1. 父类静态代码块执行:父类静态属性」。

  • 子类初始化:

    • 执行 staticStudent = new Student("静态学生"):

      • 堆中分配Student对象内存(地址0x001),初始化name为"静态学生";

      • 方法区中Child.staticStudent引用,指向堆中0x001地址;

      • 打印「创建Student对象:静态学生」。

    • 执行子类静态代码块,打印「2. 子类静态代码块执行:静态学生」。

阶段2:对象实例化阶段(每次new都执行,类加载完成后)

每次执行new关键字,都会触发该阶段,核心是在堆中创建对象、初始化实例资源,执行顺序严格遵循“父类→子类”“实例属性→构造代码块→构造方法”。

2.1 虚拟机栈操作

  • 操作:main方法入栈,栈帧中创建局部变量(如child1),初始值为null(仅存储引用,不存储对象本身)。

  • 内存变化:虚拟机栈main栈帧新增child1引用,值为null。

2.2 堆内存分配

  • 操作:JVM在堆中为Child对象分配内存(如地址0x002),包含对象头、继承自父类的实例字段、子类自身的实例字段(默认值null)。

  • 内存变化:堆中新增Child对象(0x002),实例字段instanceStudent默认值为null。

2.3 父类实例初始化(继承场景必执行)

  • 操作:先执行父类的构造代码块,再执行父类的构造方法(子类构造方法隐含super()调用,触发父类初始化)。

  • 内存变化:无新对象分配,仅执行父类实例相关逻辑,打印「4. 父类构造代码块执行」「5. 父类构造方法执行」。

2.4 子类实例属性初始化

  • 操作:执行子类实例属性的赋值语句(如instanceStudent = new Student("实例学生"))。

  • 内存变化:

    • 堆中分配新的Student对象(地址0x003),初始化name为"实例学生";

    • 堆中Child对象(0x002)的instanceStudent引用,指向堆中0x003地址;

    • 打印「创建Student对象:实例学生」。

2.5 子类构造代码块执行

  • 操作:执行子类的构造代码块,本质是编译器将构造代码块内容插入到“父类构造方法之后、子类构造方法之前”。

  • 内存变化:无新对象分配,打印「7. 子类构造代码块执行:实例学生」。

2.6 子类构造方法执行

  • 操作:执行子类构造方法的逻辑,完成对象的最终初始化。

  • 内存变化:无新对象分配,打印「8. 子类构造方法执行」。

2.7 引用赋值

  • 操作:将堆中Child对象的地址(0x002)赋值给虚拟机栈中的局部变量child1。

  • 内存变化:child1引用指向堆中0x002地址,对象创建完成。

2.8 多次创建对象(如child2 = new Child())

  • 跳过类加载阶段(静态资源已初始化,仅执行1次);

  • 重复2.1-2.7步骤,堆中分配新的Child对象(如0x004)和新的实例Student对象(如0x005);

  • 静态属性staticStudent仍指向方法区中全局唯一的0x001地址(全局共享)。

三、内存可视化(直观理解)

3.1 类加载完成后内存状态

3.2 两个Child对象创建后内存状态

四、核心规则总结(必记)

4.1 静态资源(方法区)规则

  • 执行时机:类加载的初始化阶段,第一次主动使用类时执行,仅1次;

  • 执行顺序:静态属性赋值语句 → 静态代码块(按代码书写顺序);

  • 继承规则:先父类静态 → 后子类静态(子类加载必触发父类加载)。

  • 4.2 实例资源(堆+栈)规则

  • 执行时机:每次new对象时执行,类加载完成后触发;

  • 执行顺序:父类构造代码块 → 父类构造方法 → 子类实例属性初始化 → 子类构造代码块 → 子类构造方法;

  • 内存规则:

    • 静态属性引用存储在方法区,全局唯一,所有对象共享;

    • 实例属性引用存储在堆中每个对象的内存块,对象独有;

    • 栈中局部变量仅存储对象引用,不存储对象本身。

  • 4.3 构造代码块的本质

    编译器会将构造代码块的内容,自动插入到“父类构造方法执行后、子类构造方法执行前”,与实例属性初始化语句的执行顺序由代码书写顺序决定。

    五、常见易错点(避坑重点)

  • 静态代码块/静态属性仅执行1次,无论创建多少个对象,都不会重复执行;

  • 父类的构造代码块/构造方法,会在子类实例属性初始化之前执行(子类构造隐含super());

  • final static 编译期常量(如static final String s = "abc"):在类加载的准备阶段就完成赋值,无需等待初始化阶段;若为static final Student s = new Student(),因new是运行期操作,仍在初始化阶段赋值;

  • 栈中仅存对象引用,对象的实际数据永远在堆中;方法区仅存类元数据,不存对象实例;

  • 被动使用类(如仅引用编译期常量)不会触发类的初始化阶段,静态属性不会执行赋值。

  • 六、最终运行结果(验证流程)

    1. 父类静态代码块执行:父类静态属性

    创建Student对象:静态学生

    2. 子类静态代码块执行:静态学生

    4. 父类构造代码块执行

    5. 父类构造方法执行

    创建Student对象:实例学生

    7. 子类构造代码块执行:实例学生

    8. 子类构造方法执行

    4. 父类构造代码块执行

    5. 父类构造方法执行

    创建Student对象:实例学生

    7. 子类构造代码块执行:实例学生

    8. 子类构造方法执行

    七、学习小结

    对象创建的核心是“类加载(静态资源初始化)+ 实例化(实例资源初始化)”,内存层面的关键是:方法区存类元数据和静态资源,堆存对象实例,栈存引用。理解执行顺序的核心是“静态先于实例、父类先于子类”,记住静态仅执行1次、实例每次new都执行,就能吃透所有相关面试题和代码执行逻辑。

    赞(0)
    未经允许不得转载:171主机测评 » JVM内存机制下对象创建的全流程分析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址