解析阶段是将常量池中与内存布局无关的符号引用(如类名、字段名、方法名等字符串描述)替换为JVM运行时可直接使用的直接引用(如内存地址、偏移量或句柄)的过程,分为静态解析和动态解析两类。

Java 的解析阶段(Resolution)是类加载过程中的一个关键步骤,发生在类加载的“连接”阶段内,主要任务就是将常量池中的符号引用(Symbolic Reference)替换为运行时可直接使用的直接引用(Direct Reference)。这个转换不是简单地“查表替换”,而是依赖 JVM 对类、字段、方法等结构的精确理解与验证。
什么是符号引用和直接引用?
符号引用以一组字面量形式存在,比如:
- 类或接口的全限定名(如
"java/lang/Object") - 字段的类名、字段名和描述符(如
"java/io/PrintStream.out:Ljava/io/PrintStream;") - 方法的类名、方法名和描述符(如
"java/lang/Object.toString:()Ljava/lang/String;")
这些信息在编译期生成,不涉及内存布局,与具体虚拟机实现无关。
而直接引用是 JVM 运行时能直接定位到目标的指针、偏移量或句柄,例如:
立即学习“Java免费学习笔记(深入)”;
- 指向方法区中类元数据的指针
- 指向堆中对象实例的指针
- 类中字段的内存偏移量
- 方法表中某个方法项的索引
直接引用与 JVM 实现密切相关,且必须能在当前进程地址空间中直接使用。
解析发生的时机:静态解析 vs 动态解析
Java 规范将解析分为两类:
-
静态解析:在类加载的解析阶段完成,适用于编译期可确定目标的场景,如
invokestatic、getstatic、putstatic、new指令对应的目标。此时 JVM 会立即查找并验证类、字段或方法是否存在、是否可访问,并生成直接引用。 -
动态解析:延迟到首次执行指令时才进行(也称“懒解析”),常见于
invokedynamic(配合 Bootstrap Method)、invokeinterface(部分 JVM 实现可能延迟到首次调用)、以及某些反射或 Lambda 表达式场景。这类解析由运行时链接机制支持,不强制在类加载时完成。
解析的具体过程(以方法调用为例)
当 JVM 遇到一条 invokestatic 指令,其操作数指向常量池中一个 MethodRef 符号引用时,会执行以下步骤:
- 根据类符号引用(如
"java/lang/System")加载并初始化目标类(若尚未加载) - 在该类及其父类、接口的方法表中查找匹配的方法名和描述符(注意:重载靠描述符区分,重写靠继承链+访问权限)
- 检查访问权限(如 private 方法不可被外部类调用)
- 验证方法是否为抽象(
invokestatic不允许调用抽象方法) - 成功后,在方法区中记录该方法的入口地址(如 JIT 编译后的 native 地址,或解释器入口)、vtable/itable 索引、或字段偏移量等,构成直接引用
字段解析类似,但需额外处理 static/non-static、final 字段的特殊行为(如 getstatic 解析 final 静态字段可能直接内联常量值)。
常见失败情况与验证逻辑
解析失败会抛出 IncompatibleClassChangeError 及其子类(如 NoSuchMethodError、NoSuchFieldError、IllegalAccessError),说明符号引用在运行时无法对应到合法的直接引用。典型原因包括:
- 类路径不一致:编译时有某类/方法,运行时缺失或版本不同(如删除了某个 public 方法)
- 访问控制违规:试图通过符号引用访问 private 或 package-private 成员
- 继承关系变化:父类方法被改为 private,导致子类符号引用失效
- 接口与类混淆:把接口类型当作类来 new,或反之
这些检查都在解析阶段强制执行,确保链接安全,避免运行时出现更难排查的内存错误。


















