jcmd vm.stringtable -verbose 返回空或“not supported”是因为未启用字符串去重:需同时配置-xx:+useg1gc和-xx:+usestringdeduplication,且jdk≥8u20;zgc/shenandoah不支持该功能。

直接结论:jcmd VM.stringtable -verbose 能快速看到字符串去重前后的内存节省量,但默认不开启字符串去重(String Deduplication),必须配合 G1 垃圾收集器 + 显式启用参数才生效。
为什么 jcmd VM.stringtable -verbose 返回空或提示“not supported”
常见错误现象是执行后只看到表头、无数据,或报 VM.stringtable not supported。这不是命令写错,而是当前 JVM 未启用字符串去重功能:
- G1 收集器未启用(
-XX:+UseG1GC缺失) - 字符串去重开关关闭(
-XX:+UseStringDeduplication未设置) - JDK 版本低于 8u20(该功能从 JDK 8u20 引入,JDK 9+ 默认支持但依然需显式开启)
- 使用了 ZGC 或 Shenandoah 等其他 GC,它们不支持该机制
jcmd VM.stringtable -verbose 的输出字段含义
当正确启用后,输出类似:
Count: 124567 Bytes: 18245678 Avg: 146.5 Stale: 2341 Processed: 124567 Skipped: 0 Deduplicated: 89234 Deduplicated Bytes: 12456789
关键字段解释:
-
Count:当前 StringTable 中存活的字符串对象总数 -
Deduplicated:已成功去重的字符串数量(即被指向同一 char[]/byte[] 的冗余实例数) -
Deduplicated Bytes:实际节省的堆内存字节数(注意:不是字符串对象本身大小,而是底层字符数组共享所省下的空间) -
Stale:已标记为待清理但尚未被 GC 回收的去重条目(说明去重线程和 GC 协作正常) -
Skipped:因长度太短(-XX:StringDeduplicationAgeThreshold=3默认值下,年龄不足 3 次 Minor GC 的字符串会被跳过)或内容不可比(如含非 Latin-1 字符且未启用-XX:+UseStringDeduplication的完整模式)而未参与去重的字符串数
如何让 VM.stringtable 统计真正起作用
仅加 -XX:+UseStringDeduplication 不够,必须组合以下最小参数集:
-
-XX:+UseG1GC:强制使用 G1(ZGC/Shenandoah 不支持此功能) -
-XX:+UseStringDeduplication:开启去重主线程 -
-XX:StringDeduplicationAgeThreshold=3:控制何时开始检查(默认 3,可调低至 1 加速观察,但增加 CPU 开销) - (可选)
-XX:+PrintStringDeduplicationStatistics:让 GC 日志中每轮打印去重摘要,便于长期监控
验证是否生效:jstat -gc <pid></pid> 中若出现 SDU(String Deduplication Utilization)列,且值 > 0,说明正在工作。
容易被忽略的三个现实约束
这个统计看似直观,但实际分析时容易高估收益:
- 去重只作用于
String对象的底层字符数组(value字段),不压缩字符串对象头或引用本身 —— 所以Deduplicated Bytes只反映数组节省,不是整个对象 - 去重发生在 GC 过程中(G1 的并发标记阶段之后),不是实时的;刚创建的字符串即使内容重复,也要等至少
AgeThreshold次 GC 后才可能被处理 - 如果应用大量使用
new String("xxx")或反射生成字符串,而这些字符串生命周期极短(Minor GC 就回收),那Deduplicated数几乎为 0 —— 去重对“瞬时字符串”完全无效










