动态链接是将编译期符号引用(如java/lang/string.tostring:()ljava/lang/string;)按需转换为运行时直接引用(如方法区入口地址)的过程;静态链接在类加载解析阶段完成invokestatic等指令,动态链接则在首次调用时通过vtable/itable查证invokevirtual等指令,invokedynamic最晚绑定;栈帧仅存常量池索引指针,执行时才查证并生成直接引用。

栈帧里的动态链接,不是把一串字符串直接“翻译”成地址,而是靠一套按需触发、分阶段查证的机制,把编译期留下的符号引用,变成运行时能跳转的直接引用。
符号引用和直接引用到底是什么
符号引用是编译期写死在常量池里的“描述”,比如 java/lang/String.toString:()Ljava/lang/String; ——它只说“找 String 类的 toString 方法”,不关心这个方法现在在哪块内存;直接引用则是运行时能用的“定位”,可能是一个方法区里的入口地址、对象字段的内存偏移量,或者一个指向已加载类的句柄。
两者之间没有固定映射表,转换依赖类是否已加载、是否初始化、目标方法能否被重写等实时状态。
什么时候转:静态链接和动态链接分两拨处理
- 类加载解析阶段就搞定的:invokestatic、invokespecial(构造器/私有方法)、final 方法。它们的目标唯一,JVM 在解析阶段就把常量池里的符号引用替换成真实地址,后续调用直接跳转。
- 等到第一次调用才查的:invokevirtual 和 invokeinterface。比如 animal.eat(),此时 animal 实际是 Dog 还是 Cat 还没定,必须等运行时拿到对象的真实类型,再查它的虚方法表(vtable)或接口方法表(itable),才能确定 eat() 的具体实现入口。
- 完全延迟到执行时的:invokedynamic。首次执行会触发 BootstrapMethod,生成 CallSite 并缓存目标方法,之后直接跳转——这是最灵活也最晚绑定的动态链接形式。
栈帧里存的不是地址,而是一个“常量池索引指针”
每个栈帧的动态链接部分,并不保存方法名字符串,也不保存最终地址,只保存一个轻量级引用:指向当前方法所属类的运行时常量池。
当字节码执行到 invokevirtual #25 时,JVM 就靠这个引用,快速定位到当前类的运行时常量池第 25 项,取出里面的符号引用,再走上面说的查类型、验权限、填 vtable 等步骤,最终生成可执行的直接引用。
这种设计让字节码指令通用、轻量,也支持多态调用的底层灵活性。
怎么验证这个过程在发生
用 javap -v YourClass.class 查看:
- 在 Constant pool 里找到类似 #36 = Methodref #37.#38 的条目——这就是符号引用;
- 在字节码中找到 invokevirtual #36 这样的指令——说明这条调用要靠动态链接来落地;
- 如果该方法是 final 或 static,基本能在类加载日志里看到 “resolved method”;如果是普通实例方法,首次调用时 JVM 会做 vtable 填充,之后调用就走缓存路径。
不复杂但容易忽略。











