虚方法表的底层原理
虚方法表(Virtual Method Table,简称 vtable)是 JVM 实现运行时多态的核心机制。
1. 什么是虚方法表?
Section titled “1. 什么是虚方法表?”虚方法表是一个存储类的虚方法地址的数组,每个类都有一个虚方法表,用于在运行时快速定位要调用的方法。
2. 基本原理
Section titled “2. 基本原理”2.1. 类加载时构建虚方法表
Section titled “2.1. 类加载时构建虚方法表”当类被加载到 JVM 时,会为每个类构建一个虚方法表:
class Animal { public void eat() { } // 虚方法 public void sleep() { } // 虚方法 public static void run() { } // 静态方法,不在虚方法表中}
class Dog extends Animal { @Override public void eat() { } // 重写父类方法 public void bark() { } // 新增方法}Animal 的虚方法表:
索引 0: Animal.eat()索引 1: Animal.sleep()Dog 的虚方法表:
索引 0: Dog.eat() // 覆盖了父类的 eat()索引 1: Animal.sleep() // 继承父类的 sleep()索引 2: Dog.bark() // 新增的方法2.2. 运行时方法调用流程
Section titled “2.2. 运行时方法调用流程”Animal animal = new Dog();animal.eat(); // 如何确定调用哪个 eat() ?JVM 的执行步骤:
- 获取对象的实际类型:
animal引用指向的是Dog对象 - 查找虚方法表:在
Dog的虚方法表中查找eat()方法 - 获取方法地址:从虚方法表索引 0 获取
Dog.eat()的地址 - 调用方法:跳转到该地址执行
3. 虚方法表的优化
Section titled “3. 虚方法表的优化”3.1. 继承中的索引保持一致
Section titled “3.1. 继承中的索引保持一致”子类重写父类方法时,该方法在子类虚方法表中的索引位置必须与父类保持一致,这是 JVM 实现高效多态调用的关键。
class A { public void method1() { } // 在 A 的虚方法表中:索引 0 public void method2() { } // 在 A 的虚方法表中:索引 1}
class B extends A { @Override public void method1() { } // 在 B 的虚方法表中:索引 0(与父类相同位置) @Override public void method2() { } // 在 B 的虚方法表中:索引 1(与父类相同位置) public void method3() { } // 在 B 的虚方法表中:索引 2(新增方法追加到末尾)}
class C extends B { @Override public void method1() { } // 在 C 的虚方法表中:索引 0(继续保持相同位置) public void method4() { } // 在 C 的虚方法表中:索引 3(新增方法追加到末尾)}为什么这样设计?
编译时,编译器根据引用类型(如 A)确定方法的索引位置。运行时,JVM 根据对象的实际类型(如 B 或 C)找到对应的虚方法表,然后直接通过索引获取方法地址并调用。
因为索引位置一致,无论实际对象是哪个子类,JVM 都能通过同一个索引快速定位到正确的方法实现,时间复杂度为 O(1),无需遍历整个方法表。
3.2. 内联缓存(Inline Cache)
Section titled “3.2. 内联缓存(Inline Cache)”JIT 编译器会自动缓存虚方法调用的目标地址,避免每次都查表:
for (int i = 0; i < 10000; i++) { animal.eat(); // 前几次查表,之后 JIT 自动缓存方法地址直接调用}工作原理:
-
初始阶段:前几次调用时,JVM 通过虚方法表查找方法地址;
-
JIT 介入:当检测到这段代码频繁执行(热点代码),JIT 编译器会缓存
animal.eat()的目标地址; -
优化后:后续调用直接使用缓存的地址,跳过查表过程,性能大幅提升。
重点:这是 JVM 运行时自动完成的优化,开发者不需要也无法手动干预,JVM 会根据代码执行情况智能决定何时启用内联缓存。
4. 哪些方法会进入虚方法表?
Section titled “4. 哪些方法会进入虚方法表?”4.1. 会进入虚方法表的方法(虚方法)
Section titled “4.1. 会进入虚方法表的方法(虚方法)”-
普通实例方法(非
private、非static、非final) -
被重写的方法
class Example { public void method1() { } // ✅ 虚方法 protected void method2() { } // ✅ 虚方法 void method3() { } // ✅ 虚方法(default)}4.2. 不会进入虚方法表的方法(非虚方法)
Section titled “4.2. 不会进入虚方法表的方法(非虚方法)”-
静态方法(
static) -
私有方法(
private) -
final 方法
-
构造方法
class Example { public static void staticMethod() { } // ❌ 静态方法 private void privateMethod() { } // ❌ 私有方法 public final void finalMethod() { } // ❌ final 方法
public Example() { } // ❌ 构造方法}原因:这些方法在编译时就能确定调用哪个方法(静态绑定),不需要运行时查表。