Java多态的运行时动态绑定本质是invokevirtual指令根据对象实际类型查vtable定位方法:编译期确定索引,运行期按索引查子类vtable跳转字节码入口,非if-else或反射,而是高效查表机制。

Java 中多态的运行时动态绑定,本质是 JVM 在执行 invokevirtual 指令时,根据对象**实际类型(堆中实例的类)**,查虚方法表(vtable),定位并跳转到对应子类方法的字节码入口。它不是靠 if-else 判断,也不是靠反射,而是一套编译期铺路、运行期高效查表的机制。
虚方法表(vtable)是核心数据结构
每个类在类加载阶段,JVM 就为其生成一张方法表,按声明顺序排列所有可被重写(非 private / static / final)的实例方法。表中每个槽位存的是该类对该方法的具体实现地址:
- 父类定义了
speak(),vtable 第 0 号槽填的是父类的实现地址; - 子类重写了
speak(),它的 vtable 同样第 0 号槽,填的却是子类自己的方法地址; - 新增方法(如子类独有的
fetch())会追加到表尾,不破坏已有索引位置。
invokevirtual 指令触发查表流程
当执行到类似 animal.speak() 这样的调用时:
- 编译器早已把这行代码编译为
invokevirtual Animal.speak字节码,并记录方法在 Animal 类 vtable 中的索引(比如是第 0 位); - 运行时,JVM 拿到
animal引用所指向的**真实对象**(比如是Dog实例),取出该对象所属类(Dog.class)的 vtable; - 直接用编译期确定的索引(0)去查 Dog 的 vtable,拿到
Dog.speak的入口地址,然后跳过去执行。
为什么构造器里调虚方法很危险
因为对象尚未初始化完成,子类字段可能还是默认值(0 / null),但 vtable 已被设为子类的——此时查表调用的已是子类重写的方法,而子类方法体却依赖未初始化的字段,极易引发 NPE 或逻辑错误。JVM 不阻止,但语义上已越界。
立即学习“Java免费学习笔记(深入)”;
接口多态略有不同:用 itable 而非 vtable
接口方法没有单继承线性关系,JVM 为实现类额外维护一张接口方法表(itable),记录每个接口方法到具体实现方法的映射。调用 payment.pay() 时,先定位到 payment 实际类型(如 WeChatPay),再查其 itable 找到 WeChatPay.pay 地址。性能略低于 vtable 查找,但仍是常数时间操作。


















