bigywater.tech
Java · 167

字节码:class 文件常量池到底保存了什么

class 文件的常量池里到底保存了什么?这篇用 javap -v 把一个小类的常量池按类型拆开,再用对照实验验证去重、内联和长度限制(invokedynamic 的调用点在后面一篇)。

84 项
一个只有 main 方法的 Demo 类的常量池大小
1 项
源码里写了 3 次的 "hello" 在常量池里占的 String 项数
v1
只重编译 Const 后,User 读到的 final 常量值(旧值)

魔数与版本

实测 · 本机执行输出
  JDK 21 默认编译:ca fe ba be 00 00 00 41  → 魔数 cafebabe,minor 0,major 65
  --release 8 编译:ca fe ba be 00 00 00 34  → major 52
  把 major 手工改成 70 再运行(JDK 21 最高支持 65):	java.lang.UnsupportedClassVersionError: Demo has been compiled by a more recent version of the Java Runtime (class file version 70.0), this version of the Java Runtime only recognizes class file vers

常量池的构成

实测 · 本机执行输出
  Utf8              42
  NameAndType       11
  Class             10
  Methodref         7
  String            3
  MethodHandle      3
  InvokeDynamic     2
  Integer           1
  Long              1
  Double            1
  Fieldref          1
  InterfaceMethodref1
  MethodType        1
  各类型举例:
    #1 Methodref         #2.#3 // java/lang/Object."<init>":()V
    #2 Class             #4 // java/lang/Object
    #3 NameAndType       #5:#6 // "<init>":()V
    #4 Utf8              java/lang/Object
    #7 String            #8 // hello
    #9 Integer           100000
    #10 Long              1099511627776l
    #12 Double            3.14d
    #14 Fieldref          #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
    #20 InvokeDynamic     #0:#21 // #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/Str
    #43 InterfaceMethodref#44.#45 // java/lang/Runnable.run:()V
    #64 MethodType        #6 // ()V
    #65 MethodHandle      6:#66 // REF_invokeStatic Demo.lambda$main$0:()V

去重与内联

实测 · 本机执行输出
  字面量 "hello" 在源码里写了 3 次:常量池里 String 项 1 个,对应的 Utf8 项 1 个
  main 里取常量的指令(节选):
    ldc #7 // String hello
    ldc #7 // String hello
    ldc #7 // String hello
    ldc #9 // int 100000
    ldc2_w #10 // long 1099511627776l
    ldc2_w #12 // double 3.14d
    ldc #47 // String lambda
  不进常量池的小整数: ['bipush 100', 'bipush 7']

编译期常量的固化

实测 · 本机执行输出
  第一次(常量都是 1 / v1):
  User 读到:Const.X = 1,Const.S = v1,Const.y = 1
  只重新编译 Const(改成 2 / v2),不重新编译 User,再运行 User:
  User 读到:Const.X = 1,Const.S = v1,Const.y = 2
  User 的字节码里对 Const 的引用: ['3: getstatic #13 // Field Const.y:I'] (只有非 final 的 y 是 getstatic,X 和 S 已经被直接写进了 User)

长度上限

实测 · 本机执行输出
  70000 个字符的字符串字面量:javac 报错 → constant string too long

分步演示

class 常量池
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12(javap、javac)
    • 没有验证:Record、Dynamic(condy)类型的常量池项、模块与包项(Module/Package)、常量池索引上限 65535 项、不同 javac 版本生成的常量池差异、JDK 17 与 27 的 major 版本
    京ICP备2026067502号-1京公网安备11010502063487号