java多态方法调用底层靠vtable实现:类加载链接阶段jvm静态构建固定指针数组,每个对象头存指向自身klass的指针,klass中含vtable地址;invokevirtual指令根据符号引用查vtable固定槽位完成o(1)跳转。

Java 多态方法调用的底层分派,本质是靠 vtable(虚方法表) 实现的静态结构 + 运行时查表跳转。它不是靠“猜类型”或“遍历继承链”,而是一张编译期就定好布局、类加载时就填好地址的固定数组。
vtable 是什么,谁来建、什么时候建?
vtable 是每个非接口类在 类加载链接阶段(具体是准备阶段)由 JVM 构建的一张指针数组。它不依赖对象创建,也不随实例变化——只要类被首次主动使用,这张表就已生成并固化在该类的 Klass 元数据中。
- 表中每个槽位对应一个可被重写的实例方法(public/protected、非 static、非 final、非 private),按声明顺序排列
- Object 类的 toString() 在 vtable 第 X 槽,String 类的重写版本也占据同一索引位置
- final、private、static 方法不进 vtable,它们走 invokespecial 或 invokestatic,编译期就绑定死
对象怎么找到自己的 vtable?
每个 Java 对象的对象头里,都隐式存着一个指向其运行时类元数据(Klass)的指针;而 Klass 结构中直接持有该类 vtable 的起始地址。
- Animal a = new Dog(); 中,a 引用的对象头指向 Dog.class 的 Klass,进而拿到 Dog 的 vtable 地址
- 向上转型(如 Animal a = new Dog())不会改对象头内容,所以多态调用永远基于真实类型
- 父类引用只是“看”的视角变了,对象本身仍是子类实例,vtable 指针没变
invokevirtual 怎么靠 vtable 完成一次调用?
执行 a.speak() 时,字节码中的 invokevirtual 指令按三步完成 O(1) 绑定:
- 从操作数栈取出对象引用,判空(为空则抛 NullPointerException)
- 通过引用拿到实际类型(比如 Dog),再从中获取其 vtable 地址
- 查常量池中 speak() 的符号引用,算出它在 vtable 中的固定索引,直接取该槽位的函数入口地址并跳转执行
整个过程没有递归、没有搜索,全靠预建表结构支撑,时间复杂度为常数级。
子类重写如何体现?vtable 怎么继承和覆盖?
子类 vtable 并非全新构造,而是以父类 vtable 为基础复制而来:
- 父类中被重写的方法,在子类 vtable 同一索引位置被替换成子类方法的入口地址
- 子类新增的虚方法,追加到表尾新槽位
- 未重写的方法,槽位内容与父类一致——这正是“继承可调用”的底层依据
- 接口方法不走 vtable,而是走 itable,因为需支持多实现,结构更复杂
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











