多态在JVM层面本质是通过虚方法表(vtable)实现的动态分派机制。类加载时为每个非接口类构建固定长度指针数组,槽位对应虚方法签名并填入实际入口地址;对象头隐式携带其运行时类的vtable地址;invokevirtual指令通过对象引用、vtable索引和常量池符号引用三步完成O(1)方法绑定。

多态在 JVM 层面的本质,是通过虚方法表(vtable)支持的动态分派机制实现的。它不依赖编译期类型,而是根据对象的实际运行时类型,快速定位并执行对应的方法版本。
类加载阶段就构建好方法表
每个非接口类在类加载的解析阶段,JVM 就会为其生成一张方法表——本质是一个固定长度的指针数组。表中每个槽位对应一个虚方法签名(如 toString()Ljava/lang/String;),填入的是该类中该方法的具体入口地址。
- Object 类的 vtable 中,
toString槽指向 Object 自己的实现 - String 类的 vtable 中,同一签名的槽指向 String 重写的实现
- 若子类没重写某个父类方法,该槽直接继承父类 vtable 中的地址
- static、private、final、构造器方法不进 vtable,它们走静态绑定
对象头隐式携带所属类的方法表地址
每个 Java 对象的对象头里,都存有一个指向其运行时类元数据(Klass)的指针;而 Klass 结构中又直接持有该类 vtable 的起始地址。这意味着只要拿到一个对象引用,JVM 就能立刻定位到它实际类型的方法表,无需遍历继承链。
- 例如
Parent p = new Child();,p 引用的对象头指向 Child 类的 Klass,进而指向 Child 的 vtable - 向上转型不会改变对象头内容,所以多态调用始终基于真实类型
invokevirtual 指令三步完成 O(1) 绑定
执行 p.say() 这类调用时,字节码中的 invokevirtual 指令按以下顺序工作:
- 从操作数栈取出对象引用,判空(为空则抛
NullPointerException) - 通过引用获取实际类型(Child),拿到其 vtable 地址
- 查常量池中该方法的符号引用,计算出它在 vtable 中的固定索引,直接取槽位里的函数指针并跳转执行
整个过程没有递归、没有搜索,全靠预建表结构支撑,时间复杂度为常数级。
vtable 的继承与覆盖体现“重写即替换”
子类 vtable 并非全新创建,而是以父类 vtable 为基础复制而来:
- 父类中被重写的方法,在子类 vtable 同一索引位置被替换成子类方法地址
- 子类新增的虚方法,追加到表尾新槽位
- 未重写的方法,槽位内容与父类一致——这正是“继承可调用”的底层依据
- 接口方法不走 vtable,而是用 itable,因需支持多实现










