Java虚方法表(vtable)在类加载的准备和解析阶段静态构建,是一张由JVM为每个类生成的指针数组,存储非private、非static、非final实例方法的实际入口地址,按继承顺序排列并支持重写覆盖,供invokevirtual指令通过编译期确定的索引号实现O(1)多态调用。

Java虚方法表(vtable)是在类加载的准备和解析阶段由JVM自动构建的,不是运行时动态生成,也不依赖对象实例。它的核心作用是支撑 invokevirtual 指令实现多态调用——即在运行时根据对象实际类型,快速定位并跳转到正确的具体方法实现。
虚方法表在类加载时静态构建
JVM在类加载的“准备”和“解析”阶段,为每个类(除接口外)生成一张虚方法表。这张表本质是一个指针数组,每个槽位存储该类某个虚方法(非private、非static、非final的实例方法)的实际入口地址。
- 表结构按方法声明顺序排列:先继承自父类的方法,再是本类新定义的方法;重写的方法会覆盖父类对应槽位
- final方法虽是虚方法(可被继承),但因不可重写,其vtable槽位在子类中仍指向原始实现,不会被替换
- static和private方法不进入vtable——它们在编译期就绑定符号引用,走invokestatic/invokespecial,不参与虚调用
- 接口方法不使用传统vtable,而是通过itable(接口方法表)+ 接口解析逻辑处理,机制不同
虚调用执行时如何查表
当执行 invokevirtual 指令时,JVM并不遍历继承链,而是直接根据对象的实际类(即heap中对象头里的Klass指针),取出该类的vtable,再用编译期已知的“方法在vtable中的索引号”进行查表跳转。
- 这个索引号在编译期就确定了,比如String.toString()在Object类vtable中固定占第5号槽位,所有子类vtable的第5号槽都对应各自重写的toString实现
- 查表过程是O(1)的,无需运行时搜索或匹配方法签名
- 若调用的是父类未被重写的方法(如Object.hashCode()在未重写的子类中),vtable对应槽位仍指向Object类中的原方法入口
子类vtable如何复用与扩展
子类vtable不是从零构建,而是在父类vtable基础上复制并修正:继承父类所有虚方法槽位,覆盖被重写的方法地址,末尾追加本类新声明的虚方法。
立即学习“Java免费学习笔记(深入)”;
- 例如:A有f()、g();B extends A且重写f()、新增h();则B的vtable = [B.f(), A.g(), B.h()],长度比A多1
- 这种设计让继承关系天然映射到内存布局,保证了多态调用的高效性和一致性
- 字段不参与vtable——vtable只管方法分发,字段访问走的是对象内存偏移量(由InstanceKlass描述)
注意几个常见误区
vtable机制常被误解为“运行时动态生成”或“每次调用都查找”,其实它高度静态化,关键点在于编译期索引+类加载期建表+运行时查表三者协同。
- 不是每个对象一份vtable——整个类的所有实例共享同一张vtable(存于方法区)
- 反射调用、MethodHandle、Lambda等不走vtable主路径,它们走的是解释器或专门的链接逻辑
- 即时编译器(如C2)可能进一步优化:对单实现的虚调用去虚化(devirtualize),直接内联,绕过vtable
基本上就这些。vtable是JVM实现面向对象多态的底层基石,理解它有助于看清“看似动态”的方法调用背后其实是精心组织的静态结构。










