Class 常量池、运行时常量池和字符串驻留池不是同一个结构。它们分别服务于 Class 文件表示、运行时符号解析和字符串
实例复用。本文用 javap 与 intern() 实验把三者分开。
Table of contents
Open Table of contents
三种“池”解决不同问题
| 名称 | 主要内容 | 生命周期线索 |
|---|---|---|
| Class 文件常量池 | 字面量、类名、字段和方法符号引用 | 位于 .class 文件 |
| 运行时常量池 | 加载后可解析的常量与符号引用 | 与定义类及加载器相关 |
| 字符串驻留池 | 规范化的 String 实例 | 由 JVM 管理,受可达性影响 |
javap -v 显示的是 Class 文件常量池,不是整个 JVM 已驻留字符串列表。
运行字符串驻留实验
下载字符串池样例下载,运行:
java -cp target/classes dev.elaine.jvmlab.StringPoolSample
输出为:
false
true
true
new String(...) 创建不同实例,所以第一次引用比较为 false。intern() 返回驻留池中的规范引用,因此第二次为
true。equals() 比较内容,所以第三次也为 true。
不要为了节省内存对所有外部字符串调用 intern()。它会增加池查询、延长部分字符串生命周期,并可能把高基数用户输入
变成长存数据。只有重复率高、集合受控且测量证明有收益时才考虑。
类元数据为什么增长
HotSpot 使用 Metaspace 保存类元数据,内存来自 Native 区域。动态代理、字节码生成、脚本引擎和反复创建类加载器会 让已加载类数量增长。若加载器仍被线程、静态字段或缓存引用,其类就无法卸载。
观察加载和卸载:
java -Xlog:class+load=info,class+unload=info ...
jcmd <PID> VM.classloader_stats
jcmd <PID> GC.class_histogram
GC.class_histogram 遍历堆并可能影响应用,先阅读 jcmd <PID> help GC.class_histogram 的影响说明。类元数据问题还可配合
Native Memory Tracking,但 NMT 需要启动时开启,会带来额外开销。
字符串的实际内存取决于实现
现代 HotSpot 可能使用 Compact Strings,以 byte[] 加编码标记保存只需单字节表示的内容。对象头、数组头、对齐和压缩
指针也影响占用。容量规划应使用 JFR Allocation、Heap Dump 或受控对象布局工具测量目标 JDK,不要用“每个字符固定
2 字节”估算整个服务。
下一步
阅读GC 调优方法,把对象生命周期证据转换成可回滚的参数实验。