-xx:+usestringdeduplication能省堆内存,是因为g1 gc在老年代扫描中自动识别内容相同的string底层char[]/byte[],只保留一份数组,其余string对象共享该数组,从而减少重复底层数组的内存占用。

为什么 -XX:+UseStringDeduplication 能省堆内存
Java 8u20+ 的 G1 GC 提供了字符串去重功能,它不靠用户手动 intern(),而是在 GC 过程中自动识别堆里内容相同但地址不同的 String 对象(底层是 char[] 或 byte[]),只保留一份数据,其余对象指向同一份底层数组。这对大量重复字符串(如日志字段、JSON key、HTTP header 值、数据库返回的固定枚举字符串)特别有效。
注意:该参数仅对 G1 垃圾回收器生效,启用前必须加 -XX:+UseG1GC;且只作用于堆内已晋升到老年代的字符串——新生代对象太“年轻”,G1 不会立即去重。
必须配对使用的参数组合
单独开 -XX:+UseStringDeduplication 没用,G1 不会启动去重线程。你需要显式启用 G1,并确认 JVM 版本支持(JDK 8u20+ 或 JDK 9+):
-
-XX:+UseG1GC—— 必须开启,否则参数被忽略 -
-XX:+UseStringDeduplication—— 开启去重逻辑 -
-XX:+PrintStringDeduplicationStatistics—— 推荐加上,用于验证是否生效(输出到 GC 日志)
示例启动命令:java -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics -Xms2g -Xmx2g MyApp
常见误判:明明开了却没看到效果
以下情况会导致去重“看似无效”:
- 字符串对象还没进入老年代:G1 只在老年代 GC(mixed GC 或 full GC)时扫描去重,频繁创建/丢弃的短生命周期字符串不会被处理
- 字符串底层不是共享数组:通过
new String("abc")创建的字符串,若其 char[] 是新分配的副本(比如调用了substring且未优化),则无法与常量池或其他副本去重 - 开启了
-XX:+UseCompressedStrings(JDK 7 已废弃)或使用了byte[]存储(JDK 9+ 默认),但去重仍基于内容比对,不影响逻辑;不过要注意日志里显示的“deduplicated”字节数是底层数组大小,不是String对象本身 - GC 日志没开或没查:必须配合
-Xloggc:gc.log -XX:+PrintGCDetails才能在 gc.log 里看到类似[String Deduplication: 5642K->128K(5514K)]的行
实际能省多少?别高估,但关键场景很值
去重节省的是底层数组内存,不是整个 String 对象头和引用字段。一个 String 对象本身约 24 字节(64 位 JVM + 压缩指针),但它的 char[] 可能几 KB。所以收益集中在“大量重复长字符串”的场景,比如:
- 解析千万级 JSON 数据,每个对象都有相同的
"status"、"code"字段名 - 微服务间传递的固定业务标识(如
"ORDER_CREATED"、"PAYMENT_SUCCESS")被反复构造 - 日志框架中固定模板字符串(
"Failed to connect to {}")被高频拼接
典型压测中,该参数可降低老年代堆占用 5%–15%,但前提是字符串确实重复且存活时间够长。如果应用本身字符串高度随机或生命周期极短,开它反而增加 GC 线程轻微开销,无实际收益。
最易被忽略的一点:它不改变应用语义,也不要求改代码,但依赖 G1 和 GC 行为节奏——这意味着你得看真实 GC 日志,而不是只信参数开了就“一定省”。










