目录
一、前置基础: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都执行,就能吃透所有相关面试题和代码执行逻辑。




