bigywater.tech
Java · 302

常量池解析:符号引用何时变成直接引用

类文件的常量池里写满了对别的类、方法、字段、字符串的引用,JVM 什么时候才去解析它们?缺一个类要到什么时候才会报错?这篇删掉类文件、打开类加载日志,逐个验证。

0 个
分支不执行时被加载的 Lazy 类数
76
一个小类(Main)的常量池条目数
3 个
三个 lambda 调用点生成的 Lambda 类数(循环 5 次的那个仍只有一个)

惰性解析

实测 · 本机执行输出
  不带参数(分支不执行):
    Main (已加载)
    main 开始
    Eager (已加载)
    Eager 构造
    main 结束
  带参数(分支执行):
    Main (已加载)
    main 开始
    Eager (已加载)
    Eager 构造
    Lazy (已加载)
    Lazy.hi
    main 结束

验证器提前加载

实测 · 本机执行输出
  分支不执行时加载的类(源码里 Base 是父类 LazySub 的父类,Runnable 是 LazyImpl 的接口):
    Main (已加载)
    Base (已加载)
    LazySub (已加载)
    main 开始
    main 结束
  同样的程序加 -XX:+UnlockDiagnosticVMOptions -XX:-BytecodeVerificationRemote(不验证应用类)后加载的相关类:
    Main (已加载)
    main 开始
    main 结束

解析失败

实测 · 本机执行输出
  不执行 new Gone() 时: 先不碰 Gone:main 正常运行到这里
  执行 3 次 new Gone():
    先不碰 Gone:main 正常运行到这里
    第 1 次 make():java.lang.NoClassDefFoundError: Gone cause=java.lang.ClassNotFoundException: Gone 身份码 1735600054
    第 2 次 make():java.lang.NoClassDefFoundError: Gone cause=java.lang.ClassNotFoundException: Gone 身份码 666988784
    第 3 次 make():java.lang.NoClassDefFoundError: Gone cause=java.lang.ClassNotFoundException: Gone 身份码 1414644648

常量池内容

实测 · 本机执行输出
  常量池条目数 76 ;各类型: {'Utf8': 43, 'Class': 11, 'NameAndType': 8, 'Methodref': 6, 'String': 3, 'InterfaceMethodref': 1, 'Fieldref': 1, 'InvokeDynamic': 1, 'Integer': 1, 'MethodHandle': 1}
  常量池里的前 14 个引用类条目:
    #1 = Methodref #2.#3 // java/lang/Object."<init>":()V
    #2 = Class #4 // java/lang/Object
    #7 = Class #8 // java/util/ArrayList
    #9 = Methodref #7.#3 // java/util/ArrayList."<init>":()V
    #10 = String #11 // x
    #12 = InterfaceMethodref #13.#14 // java/util/List.add:(Ljava/lang/Object;)Z
    #13 = Class #15 // java/util/List
    #18 = Fieldref #19.#20 // java/lang/System.out:Ljava/io/PrintStream;
    #19 = Class #21 // java/lang/System
    #24 = Methodref #25.#26 // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
    #25 = Class #27 // java/lang/String
    #30 = Methodref #31.#32 // java/lang/Math.max:(II)I
    #31 = Class #33 // java/lang/Math
    #36 = InvokeDynamic #0:#37 // #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
  getstatic System.out / ldc / invokestatic Math.max 在字节码里: ['0: new #7 // class java/util/ArrayList', '9: ldc #10 // String x', '11: invokeinterface #12, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z', '17: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream;', '21: invokestatic #24 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;', '24: iconst_1', '25: iconst_2', '26: invokestatic #30 // Method java/lang/Math.max:(II)I', '29: invokedynamic #36, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang', '34: invokevirtual #40 // Method java/io/PrintStream.println:(Ljava/lang/String;)V']

字符串常量

实测 · 本机执行输出
  同一个类里 "shared-literal" == 另一个类里的同一个字面量:true;new String == 字面量:false;z.intern() == 字面量:true;编译期拼接 "shared-" + "literal" == 字面量:true
  运行时拼接 half + "literal" == 字面量:false;运行时拼接再 intern:true

invokedynamic

实测 · 本机执行输出
  加载的 Main 的 Lambda 类: 3
    Main$$Lambda/0x0000008001000a08 source: Main
    Main$$Lambda/0x0000008001000c28 source: Main
    Main$$Lambda/0x0000008001001000 source: Main

分步演示

常量池解析
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:HotSpot 内部常量池缓存(ConstantPoolCache)的结构、解析失败是否按 JVMS 要求在同一个常量池项上保持「粘性」(本例每次都是新异常对象,没有看内部状态)、JDK 27 的行为差异、验证器 StackMapTable 的细节、-Xshare 对解析的影响
    京ICP备2026067502号-1京公网安备11010502063487号