解析阶段将符号引用转换为直接引用,即从编译期的文本描述(如"string.tostring:()ljava/lang/string;")转变为运行时可直接使用的内存地址、偏移量或vtable索引,按需分层完成,支撑java的多态与执行效率。

解析阶段把符号引用转成直接引用,本质是让 JVM 从“靠名字找人”变成“直接敲门入户”。编译时只知道“String 类有个 toString 方法”,但不知道它在内存哪块、有没有被重写、甚至 String 类本身可能还没加载;到了运行时,JVM 必须给出一个能立刻用的地址或偏移量,才能执行调用或访问字段。
符号引用是编译期的“纸面约定”
它存在 class 文件常量池里,纯文本描述,不绑定任何内存:
- 比如 "java/lang/String.toString:()Ljava/lang/String;",只说明“我要找 String 类的无参 toString 方法”,但此时 String 类可能尚未加载,方法体在哪、是否被子类覆盖、占用多少字节——全未知
- 再如字段引用 "value:I",只记录字段名和类型,不涉及它在对象内存布局中离对象头多远
- 这类引用与具体 JVM 内存布局无关,跨平台一致,是字节码可移植的基础
直接引用是运行期的“物理坐标”
它是真实可用的定位信息,一旦生成,就能跳转或读取,无需再查表匹配:
- 方法调用 → 指向方法区中该方法入口的指针(如 JIT 编译后的 native 地址,或解释器入口)
- 实例字段访问 → 对象内存中相对于对象头的偏移量(如 +16 字节)
- 静态字段访问 → 方法区中该变量的绝对地址
- 虚方法调用 → vtable 中对应槽位索引(如第 5 项),配合对象实际类型快速定位
转换不是一次性填空,而是按需分层落实
解析动作不扫完整个常量池,而是根据目标特性区别处理:
- 静态方法、私有方法、构造器、final 方法:类加载解析阶段就完成替换。因为目标唯一、不可重写,可提前锁定地址
- 普通实例方法(invokevirtual):不急着解析。等到首次调用时,根据对象实际类型查它的 vtable,生成对应入口的直接引用
- 接口方法或未加载类的引用:首次触发调用或访问时才尝试加载目标类,再解析;失败则抛 NoClassDefFoundError 或 IllegalAccessError
这个过程支撑了 Java 的灵活性和效率平衡
编译期不做硬绑定,所以支持多态、热部署、动态代理;运行期该早则早(静态方法)、该懒则懒(虚方法),避免无效解析,又确保每次执行都有确定落点。所谓“替换”,其实是建立一条可信映射链:类已加载、权限已验、位置已算、目标已稳。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











