JVM通过方法表实现虚方法动态分派:类加载时静态构建方法表,对象实例通过对象头指向其运行时类的方法表,invokevirtual指令根据方法签名索引查表完成O(1)运行时绑定。

JVM 通过方法表(Method Table,也称虚方法表或 vtable)实现虚方法的动态分派,这是多态的核心机制。理解这个流程的关键,在于看清类加载后方法表如何生成、对象实例如何关联、以及 invokevirtual 指令如何借助它完成运行时绑定。
方法表在类加载阶段就已静态构建
每个非接口的类在类加载的准备和解析阶段,JVM 就会为其生成一张方法表——本质是一个指针数组,每个槽位对应一个可被重写(virtual)的方法签名(如 java/lang/Object.toString()Ljava/lang/String;)。表中填入的是该类中**实际实现该方法的版本的入口地址**。
例如:
- Object 类的方法表里,toString 槽指向 Object 自己的 toString 实现;
- String 类的方法表里,同样签名的 toString 槽指向 String 重写的版本;
- 若子类没重写,则直接继承父类方法表中的对应槽位值(即指向父类实现)。
注意:静态方法、私有方法、final 方法不进入方法表,它们在编译期就确定了调用目标(静态绑定)。
对象实例隐式携带其类的方法表引用
每个 Java 对象头(Header)中都包含一个指向其运行时类元数据(Klass)的指针,而该 Klass 结构中就存有该类的方法表地址。也就是说,只要知道一个对象的引用,JVM 就能立刻定位到它所属类的方法表。
这使得 invokevirtual 指令无需遍历继承链——它不查“父类有没有”,而是直接按签名索引查当前对象所指类的方法表。
invokevirtual 指令执行三步查找
当执行类似 obj.toString() 这样的调用时,JVM 的 invokevirtual 指令按以下逻辑运行:
- 从操作数栈取出对象引用,检查是否为 null(为空则抛 NullPointerException);
- 通过该引用拿到对象的实际类型(即运行时类),获取其方法表地址;
- 根据常量池中解析出的方法符号引用(含类名、方法名、描述符),计算该方法在方法表中的索引位置,取出对应槽位的函数指针并跳转执行。
整个过程是 O(1) 时间复杂度——没有递归、没有遍历,全靠预先构建好的表结构支撑。
方法表与继承、重写的对应关系
方法表不是为每个对象单独创建,而是每个类一份(共享),但不同子类的方法表内容不同:
- 新增方法:在表末尾追加新槽位;
- 重写父类方法:复用父类同名方法的槽位索引,但填入子类自己的实现地址;
- 未重写方法:沿用父类方法表中该槽位的地址(即“继承”行为的本质);
- 接口默认方法或私有方法:不参与虚方法表,由其他机制(如 itable 或静态解析)处理。
因此,即使你声明 Object obj = new String();,invokevirtual 查的仍是 String 类的方法表,而不是 Object 的——这就是“动态绑定”的实质:看对象实际类型,而非引用类型。

















