跳转到内容

虚方法表的底层原理


虚方法表(Virtual Method Table,简称 vtable)是 JVM 实现运行时多态的核心机制。

虚方法表是一个存储类的虚方法地址的数组,每个类都有一个虚方法表,用于在运行时快速定位要调用的方法。

当类被加载到 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() // 新增的方法
Animal animal = new Dog();
animal.eat(); // 如何确定调用哪个 eat() ?

JVM 的执行步骤:

  1. 获取对象的实际类型animal 引用指向的是 Dog 对象
  2. 查找虚方法表:在 Dog 的虚方法表中查找 eat() 方法
  3. 获取方法地址:从虚方法表索引 0 获取 Dog.eat() 的地址
  4. 调用方法:跳转到该地址执行

子类重写父类方法时,该方法在子类虚方法表中的索引位置必须与父类保持一致,这是 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 根据对象的实际类型(如 BC)找到对应的虚方法表,然后直接通过索引获取方法地址并调用。

因为索引位置一致,无论实际对象是哪个子类,JVM 都能通过同一个索引快速定位到正确的方法实现,时间复杂度为 O(1),无需遍历整个方法表。

JIT 编译器会自动缓存虚方法调用的目标地址,避免每次都查表:

for (int i = 0; i < 10000; i++) {
animal.eat(); // 前几次查表,之后 JIT 自动缓存方法地址直接调用
}

工作原理:

  • 初始阶段:前几次调用时,JVM 通过虚方法表查找方法地址;

  • JIT 介入:当检测到这段代码频繁执行(热点代码),JIT 编译器会缓存 animal.eat() 的目标地址;

  • 优化后:后续调用直接使用缓存的地址,跳过查表过程,性能大幅提升。

重点:这是 JVM 运行时自动完成的优化,开发者不需要也无法手动干预,JVM 会根据代码执行情况智能决定何时启用内联缓存。

4.1. 会进入虚方法表的方法(虚方法)

Section titled “4.1. 会进入虚方法表的方法(虚方法)”
  1. 普通实例方法(非 private、非 static、非 final

  2. 被重写的方法

class Example {
public void method1() { } // ✅ 虚方法
protected void method2() { } // ✅ 虚方法
void method3() { } // ✅ 虚方法(default)
}

4.2. 不会进入虚方法表的方法(非虚方法)

Section titled “4.2. 不会进入虚方法表的方法(非虚方法)”
  1. 静态方法static

  2. 私有方法private

  3. final 方法

  4. 构造方法

class Example {
public static void staticMethod() { } // ❌ 静态方法
private void privateMethod() { } // ❌ 私有方法
public final void finalMethod() { } // ❌ final 方法
public Example() { } // ❌ 构造方法
}

原因:这些方法在编译时就能确定调用哪个方法(静态绑定),不需要运行时查表。