
java 中继承对象在堆内存中只创建一个连续对象实例,子类对象包含父类字段与自身字段的完整布局,通过统一对象头实现多态引用。
在 Java 中,当使用 new B(1, "b") 创建一个继承自 A 的子类 B 实例时,JVM 并不会在堆中分别分配两个独立对象(一个 A、一个 B),而是分配单一、紧凑的内存块,完整容纳整个继承链的所有实例字段。
内存布局结构(以 B b = new B(1, "b") 为例)
该对象在堆中的实际布局如下(忽略对齐填充和 JVM 版本差异细节):
| 内存区域 | 内容说明 |
|---|---|
| 对象头(Object Header) | 包含 Mark Word(哈希码、GC 分代年龄、锁状态等)和 Class Metadata Pointer(指向 B.class 的 Klass 指针) |
| 父类字段区 | int id(来自 class A) |
| 子类字段区 | String name(来自 class B) |
✅ 关键点:B 的实例直接内嵌了 A 的所有非静态字段(此处仅 id),而非持有对 A 对象的引用。因此,b 的内存地址即为整个对象起始地址——从该地址出发,JVM 可按偏移量精确访问 id(父类字段)或 name(子类字段)。
为什么能安全向上转型?——基于内存布局的“天然兼容”
B b = new B(1, "b"); A a = b; // 合法!无需额外拷贝或封装
之所以 B 类型引用可直接赋值给 A 类型变量,根本原因在于:
- A 的字段(id)位于对象内存的固定前缀位置;
- A 的方法表(vtable)指针可通过 B 对象头中的 Klass 结构查找到其 A 部分的虚函数入口;
- 因此,将 B 引用视为 A 引用,仅需语义层面限制可访问字段/方法范围,底层内存地址完全一致。
注意事项与常见误区
- ❌ 错误认知:“B extends A 意味着 B 中‘包含’一个 A 对象” → 实际是字段扁平化合并,无嵌套对象。
- ⚠️ private 字段仍被分配空间(如 A.id),但不可被 B 直接访问(编译器检查),仅可通过 super.id(若可见)或反射读取。
- ? 字段顺序遵循声明顺序(父类字段在前,子类字段在后),JVM 可能重排序优化,但逻辑布局保证父子字段连续。
- ? 静态字段(static)不参与实例分配,它们属于类元数据,存储在方法区(JDK 8+ 为 Metaspace)。
总结
Java 继承的内存模型是高效且直观的:单对象、多视图。每个子类实例在堆中表现为一块融合了全部祖先类(直至 Object)和自身定义的非静态字段的连续内存区域,并由统一对象头标识其真实运行时类型。这种设计既保障了 is-a 关系的语义正确性,又避免了额外的间接寻址开销,是 Java 多态实现的底层基石。
立即学习“Java免费学习笔记(深入)”;










