invokevirtual指令通过虚方法表(vtable)和对象运行时类型实现动态绑定。1. 编译阶段仅记录方法符号引用;2. 类加载阶段为每个类构建vtable;3. 运行阶段根据对象实际类型查vtable索引调用;4. JVM通过内联缓存与CHA优化提升性能。

invokevirtual 指令在运行时通过**虚方法表(vtable)** 和**对象的实际类型(运行时类型)** 动态确定要调用的方法入口,这是 Java 实现动态绑定(多态)的核心机制。
1. 编译阶段:只记录方法签名,不绑定具体实现
编译器看到 invokevirtual 时,仅根据**引用的声明类型**(即变量的静态类型)去常量池中查找对应的方法符号引用(如 Ljava/lang/Object;.toString:()Ljava/lang/String;),并生成指令。此时完全不关心实际对象是哪个子类,也不做任何实现绑定。
2. 类加载阶段:为每个类构建虚方法表(vtable)
JVM 在类加载的准备/解析阶段,为每个非 final 的、可被重写(override)的实例方法,在该类的 vtable 中分配一个固定槽位。规则如下:
- 父类中已有的虚方法,在子类 vtable 中保持相同索引位置,若被重写,则填入子类自己的方法入口地址;
- 子类新定义的虚方法,追加在 vtable 末尾;
- final 方法、static 方法、private 方法、构造器不会进入 vtable;
- vtable 是类级别的结构,每个类一份,不是每个对象一份(节省内存)。
3. 运行阶段:查表 + 偏移定位,完成动态分派
执行 invokevirtual 时,JVM 执行以下步骤:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 从操作数栈弹出对象引用,检查其是否为 null(为空则抛
NullPointerException); - 获取该对象实际所属类(例如,虽然声明为
Animal a = new Dog();,但对象实际类型是Dog); - 找到该类的 vtable(比如
Dog.class的 vtable); - 根据编译期确定的“方法在父类 vtable 中的索引”(例如
toString()在Objectvtable 中是第 5 号槽),直接访问Dogvtable 的第 5 号槽; - 取出该槽中存储的指向具体字节码的指针(可能是
Dog.toString()的入口),跳转执行。
4. 特殊优化:内联缓存(IC)与 CHA
现代 JVM(如 HotSpot)会进一步优化这个过程:
- 首次调用时,在调用点插入“内联缓存”,记录最近一次匹配的类和方法地址;
- 下次调用若对象类型相同,直接命中缓存,跳过查 vtable;
- 若频繁调用且目标类型稳定(如只有
Dog和Cat),JIT 编译器可能基于类层次分析(CHA)生成多分支快速路径,甚至对单态情况直接内联方法体。
整个过程不依赖方法名字符串匹配,而是靠编译期索引 + 运行时类结构 + 表驱动查找,既保证了灵活性,又兼顾了性能。本质上,invokevirtual 的“寻找”不是搜索,而是精准的数组索引访问。

















