(6)《常量池深度解析》

2026-03-21
2
-
- 分钟
|

一、常量池分类

类型存储位置触发时机内容生命周期线程共享
字节码常量池.class 文件编译期类名、方法名、字符串字面量等持久化磁盘
运行时常量池方法区(Metaspace)类加载时符号引用 → 直接引用JVM 运行期间
字符串常量池堆(JDK 7+)首次使用字符串String 对象引用缓存JVM 运行期间

✅ 字节码常量池:静态只读
✅ 运行时常量池:动态可扩展
✅ 字符串常量池:专用于 String 优化,是运行时常量池的特化子集


二、数据结构与解析

字节码常量池

  • .class 文件开头,索引从 1 开始

  • 结构:CONSTANT_Utf8_infoCONSTANT_String_info

  • 示例:

    #3 = String #20   // "Hello"
    #4 = Class  #21   // java/lang/System
    

运行时常量池

  • 每个类一个,位于方法区
  • 解析阶段将符号引用转为直接引用(如方法地址、字段偏移)
  • 支持动态扩展(如 invokedynamic

字符串常量池

  • 底层:哈希表(数组 + 链表)
  • JDK 6:永久代 → GC 慢,OOM 风险高
  • JDK 7+:堆中 → 受常规 GC 管理

三、intern() 行为(JDK 7+)

String s1 = new String("hello"); // 堆对象
String s2 = s1.intern();         // 返回串池中 "hello" 引用
String s3 = "hello";             // 自动入池

s1 == s2 → false  
s2 == s3 → true
  • "hello" 字面量在类加载时已入池
  • new String("hello") 创建额外堆对象
  • intern() 不新建对象,仅注册引用(JDK 7+)

四、执行流程(HelloWorld)

System.out.println("Hello, World!");
  1. 编译 → .class 中含 #3 = String #18
  2. 类加载 → 创建运行时常量池
  3. 执行 ldc #3 → 首次使用 "Hello, World!",创建对象并入串池
  4. 调用 println(方法引用已解析)

五、关键对比

维度运行时常量池字符串常量池
范围所有类/方法/字段String
内容直接引用(指针/偏移)String 对象引用
创建时机类加载时字符串首次使用(懒加载)
位置Metaspace堆(JDK 7+)
GC类卸载时释放普通堆 GC

协作链:
字面量 → 字节码常量池 → 运行时常量池 → 字符串常量池(按需)


六、典型面试题

1. 引用比较

String a = "hello";
String b = new String("hello");
String c = b.intern();
a == b → false  
a == c → true

2. 编译期折叠

String s1 = "ja" + "va"; // → "java"(编译期确定)
s1 == "java" → true

3. 运行时拼接

String x = "ja", y = "va";
String z = x + y; // StringBuilder 新建对象
z == "java" → false

七、OOM 场景与防范

运行时常量池 OOM(Metaspace)

  • 原因:动态生成大量类(CGLib、JSP)
  • 表现:OutOfMemoryError: Metaspace
  • 对策:
    • -XX:MaxMetaspaceSize=256m
    • 避免类加载器泄漏

字符串常量池 OOM

  • 原因:大量唯一字符串调用 intern()
  • 表现:堆内存耗尽
  • 对策:
    • 不对 UUID、时间戳等高熵字符串 intern()
    • 使用软引用缓存替代

八、调优与监控

建议

  • ✅ 优先用字面量(自动入池)
  • ✅ 高频重复字符串主动 intern()
  • ❌ 避免动态字符串无脑 intern()

监控命令

jcmd <pid> VM.metaspace          # Metaspace 使用
jstat -class <pid>               # 类加载统计
jmap -histo:live <pid> | grep String

关键参数

-XX:MaxMetaspaceSize=512m
-XX:StringTableSize=65536        # 默认 60013,建议质数

九、执行全景图

源码 
→ .class(字节码常量池) 
→ 类加载(运行时常量池) 
→ 解析(符号→直接引用) 
→ 首次 ldc(字符串入池) 
→ 执行引擎调用

核心:常量池是 JVM “符号 → 实体” 的转换枢纽。
掌握三层结构 + intern() 差异 + 内存模型,即可精准调优。

评论交流

文章目录