动态链接是Java多态的核心机制,它在运行时根据栈帧中对象的实际类型,将符号引用实时解析为直接引用,从而实现父类引用调用子类方法。

栈帧的动态链接是Java多态得以运行的关键支撑,它让同一个方法调用指令,在不同对象实例上能触发不同的实际行为。理解这点,就等于摸清了“为什么父类引用能调用子类方法”的底层开关。
动态链接:符号引用到直接引用的实时转换
每个栈帧都持有一个指向运行时常量池的引用,这个引用就是动态链接的载体。当字节码中出现invokevirtual这类指令时,JVM不会在编译期就锁定具体调用哪个方法,而是在运行时,根据当前栈帧中对象的实际类型(即invokevirtual指令操作数栈顶的对象引用),去常量池里查找该类型对应的**实际方法入口**。
这个过程不是每次调用都重复查表——JVM会做优化,比如使用虚方法表(vtable)或内联缓存(IC),但本质仍是:调用发生时,才依据对象真实类型确定目标方法。
多态变量执行的三步落地
所谓“多态变量”,通常指声明为父类/接口类型、实际指向子类对象的引用。它的执行逻辑依赖动态链接完成以下衔接:
-
变量存储在局部变量表中,但值是指向堆中对象的引用:声明类型(如
Animal a)只影响编译期检查和局部变量槽位分配,不决定运行时行为。 -
调用方法时,操作数栈顶必须是该对象引用:字节码执行
invokevirtual前,会把对象引用压入操作数栈;JVM据此获取其真实类信息(比如是Dog而非Animal)。 -
动态链接查出该类中重写后的方法地址,并跳转执行:哪怕字节码里写的是
Animal.speak(),只要运行时对象是Dog实例,就会找到Dog.speak()的字节码并执行。
静态分派与动态分派的分工边界
重载(overload)是静态分派,在编译期靠参数的声明类型决定调用哪个方法签名,跟栈帧动态链接无关;而重写(override)才是动态分派,完全依赖运行时栈帧中对象的实际类型,由动态链接机制完成绑定。
也就是说:变量声明类型管“能不能编译过”,对象实际类型管“到底执行谁”。动态链接就是那个在方法调用瞬间,读取实际类型、查表、跳转的执行引擎“扳道工”。
一个直观例子:看字节码怎么靠动态链接转向
假设有Animal a = new Dog(); a.speak();:
- 编译生成的字节码是
invokevirtual Animal.speak:()V,常量池里记录的是Animal类下的符号引用。 - 运行时,JVM进入
a.speak()指令,从操作数栈拿到a引用,发现它指向堆中Dog实例。 - 通过该实例的类元数据,定位
Dog类的虚方法表,查到speak()项对应的是Dog.speak的字节码地址。 - 于是新建栈帧,执行
Dog.speak——整个过程对源码透明,全由动态链接驱动。
不复杂但容易忽略:动态链接不是一次配置,而是每次虚方法调用都发生的实时解析动作;它让栈帧成为类型信息与执行逻辑之间的活桥梁。

















