子类对象实例化分四步:类加载(方法区存元数据)、堆内存分配(对象头+字段,默认零值)、构造器调用链(父→子逐层初始化)、栈中存引用。

子类对象实例化时,内存分配分四步走
子类对象创建时,并不是简单地“new 一个子类”,而是经历加载、分配、初始化、链接的完整过程。整个过程中,内存分布在方法区、堆、栈三处,各司其职。
类加载阶段:方法区存结构,不占对象空间
当首次用到子类(比如 new Child())时,JVM 先检查该类是否已加载。若未加载,则触发类加载流程:
- 从 classpath 加载 Child.class 和其父类 Parent.class 的字节码
- 将类的元数据(字段名、方法签名、常量池、静态变量等)存入方法区(Java 8+ 是元空间)
- 为每个类生成一个 java.lang.Class 实例,也放在堆中(注意:Class 对象本身在堆,但描述的是类结构)
这一步不为子类对象分配实例内存,只是准备好“模板”。方法代码(包括父类和子类的所有实例方法)只加载一次,所有对象共享。
堆内存分配:只存字段和对象头,不含方法
执行 new Child() 时,JVM 在堆中划出一块连续内存,仅用于存放:
立即学习“Java免费学习笔记(深入)”;
- 对象头:包含哈希码、GC 分代年龄、锁标志位,以及一个指向方法区中 Child.class 元数据的指针(注意:它指向的是子类自己的 Class 对象,不是父类)
- 实例字段:先按继承顺序排列——父类字段在前,子类字段在后;再按宽度排序(long/double → int → short/char → byte/boolean),以保证内存对齐
- 对齐填充(可选):确保对象总大小是 8 字节的整数倍
字段值此时被设为默认零值(如 int=0,Object=null),尚未执行任何构造逻辑。此时对象已“存在”,但尚未“就绪”。
构造器调用链:从父类到子类逐层初始化
内存分配完成后,JVM 按照继承链自上而下执行构造器:
- 先隐式或显式调用 super(),进入父类构造器
- 父类构造器内:给父类字段赋初值(如 int x = 5;)、执行父类构造体代码块和构造方法体
- 回到子类构造器:给子类字段赋初值(如 String name = "Tom";)、执行子类构造体代码块和构造方法体
特别注意:如果父类构造器中调用了被子类重写的方法,此时子类字段可能仍是默认值(如 0 或 null),因为子类字段初始化尚未发生——这是常见陷阱。
栈中保存引用,不存对象本体
声明语句 Child c = new Child(); 中:
- c 这个变量本身存于当前方法的栈帧中,它只是一个 4 字节(32 位)或 8 字节(64 位,开启指针压缩时通常也是 4 字节)的引用值
- 这个引用指向堆中刚分配好的那块子类对象内存地址
- 栈内存随方法退出自动释放;堆中对象是否存活,取决于是否有可达引用(如 c 是否还在作用域内、是否被其他对象持有等)
也就是说,你看到的“c”不是对象,只是通往对象的一把钥匙。


















