解析阶段的核心是将常量池中的符号引用(如“java/lang/string.value:i”)按需转换为直接引用(如字段偏移量+12、方法区指针0x7f00a120),需触发类加载、结构匹配、权限校验,生成运行时可直接定位的物理地址。

解析阶段的核心,是把编译期写死在常量池里的“名字描述”,变成运行时能直接定位的“物理位置”。它不是简单替换字符串,而是建立一条可信的映射链:类已加载、权限已验、结构已知、位置已算。
符号引用到底是什么
它是纯字面量,和内存无关。比如常量池里一条 CONSTANT_Fieldref_info 记录:"java/lang/String.value:I",只说明“String 类有个叫 value 的 int 字段”,但此时 String 类可能还没加载,value 在对象里偏多少字节?完全未知。这类信息只靠语义匹配存在,不绑定任何地址。
解析怎么一步步落实转换
JVM 遇到一个符号引用时,会按顺序做这几件事:
- 从常量池提取全限定类名(如 "java/lang/String"),触发该类加载(若尚未加载)
- 在方法区中查找到该类的运行时数据结构(HotSpot 中是 Klass* 指针)
- 遍历该类及其所有父类的字段表或方法表,严格匹配字段名 + 描述符(如 "value:I")
- 检查访问控制(比如当前类能否访问 private 字段),失败则抛 IllegalAccessError
- 成功后,生成对应类型的直接引用:字段 → 对象内偏移量(如 +12),静态变量 → 方法区指针,方法 → Method* 或 vtable 槽位索引
不是一次性全换,而是按需分层解析
解析动作不扫完整个常量池,而是依目标特性区别处理:
- 静态方法、私有方法、构造器、final 方法:类加载的解析阶段就完成替换——目标唯一、不可重写,可提前锁定入口地址
- 普通实例方法(如 invokevirtual 调用):不急着换,等真正调用时才看对象实际类型,查它的虚方法表(vtable),生成该类型下方法的真实入口
- 接口方法或未加载类的引用:首次触发调用或访问时才加载目标类,再解析;失败则抛 NoClassDefFoundError
直接引用长什么样
它不是抽象概念,而是 JVM 内存中真实可用的定位信息:
- 指向方法区中某个方法结构体的指针(如 0x7f00a120)
- 对象实例中字段相对于对象头的偏移量(如 +8 字节)
- 虚方法表(vtable)中的槽位编号(如第 5 项)
有了它,后续每次调用或访问,JVM 就不再查类、不匹配名字,直接跳转或读取。











