动态绑定的本质是JVM在类加载时构建虚方法表(vtable),运行时通过对象指向的vtable直接查槽位获取方法地址,不遍历继承链;vtable为一维数组,索引由方法声明顺序决定,父类与子类同签名方法共享索引,static/final/private方法不入表。

动态绑定的本质是运行时方法表查找
Java的动态绑定不是在调用时“实时计算”方法地址,而是JVM在类加载阶段就为每个类构建好一张虚方法表(vtable),其中按声明顺序存放所有可被重写的方法入口。对象实例只持有指向其实际类型vtable的指针。当执行invokevirtual指令时,JVM直接查该vtable对应槽位,拿到目标方法的字节码起始地址——整个过程不涉及遍历继承链或反射解析。
vtable结构决定多态调用的性能与行为
vtable不是HashMap,它是一维数组,索引由方法签名在类定义中的首次出现顺序决定。父类方法和子类重写方法共享同一索引。例如:
- class A { void m() {} } → vtable[0] = A.m
- class B extends A { void m() {} } → vtable[0] = B.m(覆盖而非新增)
- class C extends B { void n() {} } → vtable[1] = C.n(新增方法排在后面)
注意:static、final、private方法不入vtable;接口默认方法和私有方法也不参与动态绑定。
用javap观察vtable生成痕迹
虽然JVM规范未强制公开vtable内存布局,但可通过javap -v反编译查看关键线索:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
- 查看
Constant Pool中方法引用是否指向子类实现(如#4 = Methodref #2.#3 // B.m:()V) - 搜索
invokevirtual字节码操作数,它引用的是常量池中已解析好的符号引用,而非源码中的父类名 - 对比父类与子类的
Code段,相同签名的方法在不同类中拥有独立的Code属性,说明JVM在链接阶段已完成具体版本绑定
真正影响绑定结果的只有运行时对象的实际类型
变量声明类型仅用于编译期检查,对vtable查找无任何影响。以下代码无论怎么写声明,最终都调B.m:
- A a = new B(); a.m();
- Object o = new B(); ((A)o).m();
- List list = Arrays.asList(new B()); list.get(0).m();
只要实例是B类型,它的对象头就指向B类的vtable,invokevirtual查的就是B.vtable[0]。没有“向上转型导致查找变慢”的说法——vtable跳转是单次指针偏移,开销恒定。

















