继承、多态、抽象类直接映射JVM运行时机制:子类对象在堆中按“父前子后”布局,vtable在方法区实现动态绑定,抽象类通过ACC_ABSTRACT标记和常量池约束在加载阶段即阻断实例化,接口则依赖itable支持多实现。

继承、多态、抽象类不是语法糖,而是直接映射到 JVM 运行时内存模型中的结构性机制。理解它们,关键不在于背定义,而在于看清对象在堆、方法区、虚拟机栈里怎么“落位”、怎么“联动”。
对象创建时:堆中结构决定继承的物理基础
子类实例在堆中是一块连续内存,但内部按继承链分层布局:
- 最顶部是父类字段(含从 Object 继承的 mark word 和 klass pointer)
- 中间是父类显式声明的非静态字段(按声明顺序排列,含 inherited 的 protected/default 字段)
- 底部是子类自己新增的字段
这种“父前子后”的内存排布,使得 父类引用能安全指向子类对象——因为父类字段区域完全兼容,指针偏移量一致。super() 调用本质就是让构造器按此布局顺序初始化各层字段。
方法调用时:方法区 vtable 是多态的运行底座
JVM 为每个类在方法区维护一张虚方法表(vtable),表中每一项是方法符号引用到实际字节码入口的映射:
- 父类 vtable 包含所有可被重写的方法槽位(如 Animal.eat)
- 子类 vtable 复制父类表,但将被重写的方法槽位替换为子类版本地址(如 Dog.eat)
- 多态调用(animal.eat())不查 animal 变量类型,而是查它所指对象的实际类(Dog.class)的 vtable,再跳转到对应槽位
这就是“动态绑定”的物理实现——运行时根据对象头里的 klass pointer 找到类元数据,再索引其 vtable,和编译时的变量声明类型无关。
抽象类加载时:方法区常量池与类状态标记支撑语义约束
抽象类在类加载阶段就被 JVM 标记为 ACC_ABSTRACT,并且其抽象方法在常量池中无 Code 属性:
- 当 ClassLoader 解析抽象类字节码时,会检查是否有未实现的抽象方法;若有,且该类未被声明 abstract,则抛 VerifyError
- 抽象类的 vtable 中,抽象方法槽位保留为空(或填入 throw AbstractMethodError 的桩代码),强制子类覆盖时才填入真实入口
- new 抽象类实例的字节码指令(new + dup + invokespecial)在解析阶段就会被拒绝——JVM 直接禁止在堆中分配抽象类对象
所以抽象性不是靠运行时检查,而是由类文件结构 + 方法区元数据 + 字节码验证三者协同,在加载和验证阶段就物理阻断非法使用。
接口与抽象类差异:vtable vs itable 的双表机制
Java 支持单继承但允许多实现接口,这靠的是两套并行查找表:
- vtable 仍用于继承链上的方法(父类 → 子类)
- itable 单独存在,记录当前类实现的每个接口中方法到具体实现的映射
- 当通过接口引用调用方法(如 List.add()),JVM 先用对象 klass pointer 查 itable,再定位实现
也就是说,多态既可通过继承链(vtable)发生,也可通过实现关系(itable)发生,二者共存于方法区,互不干扰。这也是抽象类不能多继承、接口却能多实现的底层原因——vtable 是线性结构,itable 是哈希+数组混合结构,天然支持多源归并。

















