vtable由JVM在类加载链接阶段静态构建:按声明顺序收集可重写实例方法,子类复制父类表后覆盖重写方法槽位或追加新方法;final/private/static方法不入表,走静态绑定。

编译器本身不构建 vtable 或 itable——这是 JVM 在类加载的链接阶段完成的工作。Java 编译器(javac)只负责生成字节码和符号引用,真正的虚方法表和接口方法表由 HotSpot 等 JVM 实现,在运行时根据类结构静态生成并存入方法区(元空间)。
vtable 是怎么构建的
vtable 按类声明顺序收集所有可被重写的实例方法(public/protected、非 static、非 final、非 private),每个槽位对应一个方法在该类中的实际实现入口:
- 子类 vtable 初始化时,先复制父类 vtable 的全部条目
- 若子类重写了某个方法,就用子类版本覆盖对应槽位
- 若子类新增了可重写的方法(未在父类中声明),则追加到表尾
- final、private、static 方法不进 vtable,它们走静态绑定(invokespecial / invokestatic)
- vtable 存储在 InstanceKlass 对象末尾,类加载准备阶段就固定,所有该类实例共享同一张表
itable 是怎么构建的
itable 解决的是“一个类实现多个接口”带来的方法定位不确定性问题,它不是一张扁平表,而是分层结构:
- 每个实现的接口,在 itable 中对应一个 itableOffsetEntry:记录接口类型指针 + 该接口对应方法表的起始偏移
- 紧随其后是该接口的 itableMethodEntry 数组:每个 entry 存一个 Method* 指针,指向该接口方法在此类中的具体实现
- 只有 public abstract 接口方法、以及被该类显式实现(或继承 default 实现)的接口方法才会进入 itable
- 接口中的 static 方法不进 itable,也不进任何实例方法表;default 方法会进入实现类的 vtable,而非 itable
- 当多个接口定义同签名 default 方法时,编译器强制要求类中 override 显式选择,否则编译失败
什么时候查 vtable,什么时候查 itable
JVM 根据 invokevirtual 或 invokeinterface 指令的语义决定查哪张表:
- invokevirtual:用于普通类方法调用。JVM 查对象实际类型的 vtable,按方法在类中声明的索引定位槽位
- invokeinterface:用于接口引用调用。JVM 先在对象实际类型的 itable 中找目标接口的 offset entry,再跳转到对应 method entry 获取实现地址
- 即使变量声明为接口类型,只要字节码指令是 invokevirtual(比如调用 Object 方法 toString()),仍走 vtable
关键细节不能漏
vtable 和 itable 都不是运行时动态计算的,也不是每次 new 对象都重建:
- 它们在类首次主动使用前,于类加载的“链接”阶段(验证 → 准备 → 解析)中一次性构建完成
- 构建依据是类文件的常量池、字段表、方法表,以及其父类和所实现接口的完整元信息
- 一旦构建完成,vtable/itable 内容就固化在元空间中,后续所有实例都复用同一份指针
- 字段访问(getfield)从不查这两张表——字段不支持多态,只看引用的声明类型

















