跳到正文
Elaine Blog
返回

常量池、字符串与类元数据的内存代价

JVM 与性能

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 调优方法,把对象生命周期证据转换成可回滚的参数实验。


分享这篇文章:

上一篇
CPU 飙高、死锁和长暂停排查
下一篇
GC 调优方法:目标、基线、变更与复测