jvm必须用invokevirtual实现多态:编译期仅知声明类型,运行时才通过对象头定位实际类的vtable,按签名索引查槽位获取方法地址,从而动态绑定重写版本。

Java虚拟机在执行含 this 的实例方法调用时,只要该方法是非静态、非私有、非 final、非构造器,就会生成 invokevirtual 指令——它不是直接跳转到某个固定地址,而是靠运行时对象的真实类型查表决定调用哪个版本,这才是多态落地的关键。
为什么必须用 invokevirtual 而不是直接调用?
编译期只知道变量的声明类型(比如 Animal a = new Dog(); 中的 Animal),但无法预知运行时 a 指向的是 Dog 还是 Cat。如果硬编码成调用 Animal.speak(),就无法体现重写行为。JVM 必须把绑定动作推迟到运行时,等拿到对象头里的实际类信息后,再做决策。
执行时怎么快速找到 Dog.speak()?
JVM 不会每次调用都遍历继承树。每个类加载时,JVM 就为其构建一张虚方法表(vtable):
- vtable 是一个函数指针数组,按方法签名顺序排列(如 toString、equals、speak 等)
- Dog 类的 vtable 继承自 Animal,但把 speak() 对应槽位替换为 Dog.speak 的入口地址
- 执行 a.speak() 时:先从 a 的对象头取出实际类(Dog.class)→ 定位 Dog 的 vtable → 根据常量池中 speak() 的符号引用算出索引 → 直接取该索引处的地址跳转
它只看 this 的实际类型,不看参数
invokevirtual 是动态单分派:唯一依据就是接收者(即 this)的实际类。哪怕你写了多个 speak(String) 和 speak(int),编译器早已根据参数的静态类型选好签名;invokevirtual 解决的只是“这个 speak() 到底是 Dog 版本还是 Cat 版本”的问题。
和 invokespecial 的本质区别在哪?
invokespecial 用于构造器、私有方法或 super.xxx() 调用,它在解析阶段就能确定唯一目标,属于静态绑定;而 invokevirtual 的目标方法在类加载时无法确定,必须等对象实例创建后,结合其真实类型才能定位——这就是“虚”的含义:虚方法表 + 运行时查表 + 动态绑定。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











