java中只有非private、非static、非final的实例方法才进入vtable,该表在类加载linking阶段生成,由jvm自动构建并供invokevirtual指令运行时查表调用。

Java继承中的方法绑定,核心在于区分静态绑定和动态绑定,而虚函数表(vtable)是支撑动态绑定的关键底层机制。它不靠语法糖或手动配置生效,而是JVM在类加载阶段自动生成、运行时自动查表执行的结果。
哪些方法会进入虚函数表
只有满足以下全部条件的实例方法,才会被纳入该类的vtable:
- 不是 private 方法(private无法被继承,自然不参与多态)
- 不是 static 方法(static属于类级别,绑定发生在编译期)
- 不是 final 方法(final禁止重写,失去动态分派意义)
- 是实例方法(即非静态、有明确接收者 this)
vtable 是什么时候生成的
vtable 在类加载的 linking 阶段完成构建,不是每次 new 对象时才创建。每个类(包括接口,对应 itable)都有独立的一张表,由该类对应的 Class 对象持有,所有该类的实例共享同一张表。
子类 vtable 的生成分四步:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 先拷贝父类 vtable 的全部条目
- 用子类中重写的方法地址,覆盖对应槽位(方法名+签名完全一致)
- 将子类新声明的、可被重写的方法追加到表尾
- 对未实现但必须存在的接口默认方法(Miranda 方法),补全入口
调用时怎么找到真正要执行的方法
当执行 invokevirtual 指令(比如 obj.doWork())时,JVM 做三件事:
- 从操作数栈取出 obj 引用,获取其实际类型(比如是
Dog类,而非声明类型Animal) - 定位该类型的 Class 对象,拿到它的 vtable
- 根据方法在源码中声明的顺序,确定其在 vtable 中的固定槽位索引,直接跳转到该槽位存储的地址
注意:槽位编号与继承链无关,只取决于当前类定义里方法的书写顺序。
容易踩坑的几个关键点
理解 vtable 后,下面这些现象就不再“反直觉”:
- 构造器里调用可重写方法很危险:此时子类字段尚未初始化,但 vtable 已就绪,子类重写的方法会被调用,可能访问 null 或默认值
- static 方法看似“重写”,实为隐藏:它不进 vtable,调用取决于引用类型,不是实际类型
- NullPointerException 后才发现调用的是父类方法:往往是因为对象根本没成功创建(如构造失败、返回 null),导致调用发生在 null 引用上,触发 NPE,而非走到了子类逻辑
- final 方法性能更优:JVM 可以安全内联,跳过 vtable 查表开销
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










