动态链接是将字节码中无地址的符号引用在运行时解析为内存地址或偏移量的过程;它由JVM按需懒解析,通过栈帧指向运行时常量池实现高效复用与多态支持。

动态链接的核心,是把字节码里那些“只知道名字、不知道地址”的符号引用,在运行时变成能直接跳转执行的内存地址或偏移量。这个过程不是编译时定死的,而是由 JVM 在类加载或首次调用时按需完成的。
符号引用长什么样?
它是一组字符串描述,存在 class 文件的常量池中,和内存布局无关:
- 类引用:比如 "java/lang/System"
- 字段引用:"out:Ljava/io/PrintStream;"
- 方法引用:"println:(Ljava/lang/String;)V"
在字节码里就表现为 #3、#5 这样的索引,指向常量池某一项——它本身不包含任何地址信息,只是个“代号”。
什么时候开始解析?
JVM 采用懒解析策略,不是一加载就全干完,而是等真正要用的时候才动手:
- new 指令触发类的解析(确保目标类已加载)
- getstatic / putstatic 触发静态字段解析
- invokestatic 触发静态方法解析
- invokevirtual / invokeinterface 触发实例方法符号引用解析(但不决定具体实现类,那是虚方法表的事)
- 反射调用(如 Class.forName()、Method.invoke())也会隐式触发
解析到底做了什么?
以 invokestatic #5 为例,JVM 实际执行以下动作:
- 查常量池第 5 项,拿到符号引用(如 "java/lang/System.out:Ljava/io/PrintStream;")
- 根据类名 "java/lang/System" 确保该类已加载、验证、准备完毕
- 在 System 类的数据结构中定位到 out 这个静态字段
- 检查访问权限(public 字段才能成功解析)
- 生成直接引用:可能是堆中 PrintStream 对象的起始地址,也可能是类数据区中该字段的固定偏移量
- 把结果缓存回常量池对应位置,下次再用 #5 就直接取地址,不再重复查找
为什么需要指向运行时常量池的引用?
每个栈帧都持有一个指向运行时常量池的引用,目的就是支持动态链接。当方法体内执行到 invokevirtual #7 时,JVM 可以立刻通过栈帧里的这个指针,找到当前方法所属类的运行时常量池,再根据 #7 查出符号引用,进而完成解析或复用已有直接引用。这使得方法调用既灵活(支持多态、热替换),又高效(缓存加速)。

















