Java多态底层靠vtable驱动,但仅适用于可重写的方法;final、static、private方法不入vtable,接口方法走itable,vtable在类加载时生成且固定大小,所有同类型对象共享同一张表。

Java多态的底层靠虚方法表(vtable)驱动,但不是每个类都有独立vtable,而是每个类加载时由JVM生成一张方法分发表,运行时通过对象头里的类指针查表跳转。
为什么invokevirtual指令要查vtable而不是直接跳转
因为编译期无法确定最终调用哪个实现——子类可能重写父类方法,而引用类型和实际类型可能不一致。JVM在类加载阶段就为每个类构建vtable,把所有可被重写的方法按声明顺序填入,空位填Object的默认实现或抛AbstractMethodError。运行时拿到对象头中的klass指针,定位到对应类的vtable,再按方法在类中声明的索引查目标地址。
- 只有
public、protected和包级可见的非static/final/private方法才进vtable;private和static走invokespecial或invokestatic,不查表 - vtable大小在类加载时固定,不会因子类新增方法而改变父类vtable结构
- 接口方法不进类vtable,而是走
invokeinterface+ itable(另一张表),机制不同
final方法为什么不出现在vtable里
因为final方法不能被重写,JVM可直接内联或生成静态绑定调用,跳过查表开销。即使你写了obj.doSomething()且doSomething是final,字节码里也可能是invokespecial而非invokevirtual。
- 反编译看字节码最准:用
javap -v确认调用指令类型 - 哪怕父类方法非
final,子类重写后加final,该子类vtable里仍保留入口——只是这个入口不会再被子子类覆盖 - HotSpot对
final方法有额外优化,比如C1编译器会优先尝试去虚拟化(devirtualize)
子类没重写父类方法时,vtable里填的是父类实现地址
不是“留空”或“指向自己”,而是直接复制父类vtable对应槽位的函数指针。所以哪怕你只写了个空extends A,子类vtable长度和父类一致,大部分槽位内容相同。
立即学习“Java免费学习笔记(深入)”;
- 新增方法会追加到vtable末尾,不影响原有索引——这也是为什么字段和方法在字节码里必须按声明顺序排列
- 如果父类方法在子类中被
default接口方法“覆盖”(如同时实现两个含同名default方法的接口),JVM会在链接阶段报IncompatibleClassChangeError,不会等到运行时才崩 - 对象内存布局中,vtable指针存在类元数据区(Metaspace),不在堆里;每个类一份,所有该类实例共享
真正容易被忽略的是:vtable只解决单继承下的方法分发,而接口、Lambda、动态代理这些场景全走另一套机制。别看到多态就下意识想vtable——它只是JVM多态实现的一块拼图,不是全部。








