符号引用是编译期的逻辑描述,如“String.toString”,不绑定内存;直接引用是运行期的物理定位,如指针或偏移量;解析按需分层:静态方法类加载时替换,实例方法首次调用时解析,接口方法首次触发时加载并解析。

本质是把编译期“能说清楚是谁”的逻辑描述,变成运行期“一脚就能踩到位置”的物理定位。
符号引用只是“名字+说明书”,不绑定内存
它存于 class 文件常量池里,比如 "java/lang/String.toString:()Ljava/lang/String;",只说明“找 String 类的 toString 方法”,但此时 String 类可能还没加载,方法在内存哪块、占几个字节、有没有被子类重写——全都不知道。它和内存布局完全脱钩,纯靠字符串匹配和语义规则存在。
直接引用是“门牌号+楼层号+房间号”式的硬地址
它不是抽象概念,而是真实可用的定位信息,例如:
- 指向方法区中某方法入口的指针(如 0x7f00a120)
- 对象实例中某个字段相对于对象头的偏移量(如 +12 字节)
- 虚方法表(vtable)里的槽位索引(如 第3项)
有了它,JVM 执行 invokevirtual 或访问字段时,不用再查类、不需再匹配名字符号,直接跳转或读取。
替换不是“统一填空”,而是按需分层落实
解析动作不是一次性扫完整个常量池,而是根据目标特性分策略处理:
- 静态/私有/构造器/finall 方法:类加载解析阶段就完成替换——目标唯一、不可重写,可提前锁定内存地址
- 普通实例方法:不急着换,等真正调用时才查对象实际类型,再查它的 vtable,生成对应入口的直接引用
- 接口方法或未加载类的引用:首次触发时才加载目标类,再解析;失败则抛 NoClassDefFoundError 或 IllegalAccessError
这个过程让 Java 既保灵活又不丢效率
编译期不做硬绑定,所以支持多态、热替换、动态代理;运行期该懒则懒、该早则早,避免无效解析,又确保每次调用都有确定落点。所谓“替换”,不是改一个值,而是建立从符号到物理世界的可信映射链——类已加载、权限已验、位置已算、目标已稳。

















