bigywater.tech
Java · 297

字符串去重:相同字符数组如何共享

大量内容相同的 String 能自动合并吗?哪些收集器支持?合并的是对象还是数组?阈值怎么影响什么时候发生?这篇用已用堆数字与去重线程的日志验证。

29.1 → 10.8 MB
G1 开启字符串去重前后的已用堆(30 万个 String,100 种内容)
5 种
两个 JDK 版本里都支持字符串去重的收集器数量
300000
去重后仍然不同的 String 对象身份数

去重前后的堆

实测 · 本机执行输出
  G1,不开启去重                         已用堆 [28.9, 29.1, 29.1, 29.1, 29.1, 29.1, 29.1, 29.1]   String 对象身份数 300000,equals 去重后 100;日志显示启用=False
  G1,-XX:+UseStringDeduplication   已用堆 [26.1, 10.8, 10.8, 10.8, 10.8, 10.8, 10.8, 10.8]   String 对象身份数 300000,equals 去重后 100;日志显示启用=True
  去重线程日志(Concurrent String Deduplication 行)累计:(批次数, 检查的 String 数, 去重的 String 数) = 300302

各收集器

实测 · 本机执行输出
  JDK 21 UseSerialGC      不开去重 30.2 → 30.5 MB;开去重 30.2 → 12.2 MB;日志启用=True ;去重统计 300305
  JDK 21 UseParallelGC    不开去重 30.1 → 30.3 MB;开去重 30.1 → 12.0 MB;日志启用=True ;去重统计 300305
  JDK 21 UseG1GC          不开去重 28.9 → 29.1 MB;开去重 26.0 → 10.8 MB;日志启用=True ;去重统计 300303
  JDK 21 UseZGC           不开去重 38.0 → 40.0 MB;开去重 38.0 → 20.0 MB;日志启用=True ;去重统计 300272
  JDK 21 UseShenandoahGC  不开去重 35.1 → 27.9 MB;开去重 35.1 → 9.6 MB;日志启用=True ;去重统计 300283
  JDK 27 UseSerialGC      不开去重 27.0 → 27.4 MB;开去重 27.0 → 11.3 MB;日志启用=True ;去重统计 300190
  JDK 27 UseParallelGC    不开去重 27.0 → 27.3 MB;开去重 27.0 → 11.3 MB;日志启用=True ;去重统计 300190
  JDK 27 UseG1GC          不开去重 26.6 → 26.9 MB;开去重 25.9 → 10.9 MB;日志启用=True ;去重统计 300186
  JDK 27 UseZGC           不开去重 34.0 → 36.0 MB;开去重 34.0 → 20.0 MB;日志启用=True ;去重统计 300162
  JDK 27 UseShenandoahGC  不开去重 29.3 → 26.3 MB;开去重 29.5 → 10.4 MB;日志启用=True ;去重统计 300190

年龄阈值

实测 · 本机执行输出
  AgeThreshold=1   第一次大批量去重(检查 > 10 万个 String)发生在 GC(2) 之后:检查 106777 个、去重 106545 个;共 49 次年轻代 GC
  AgeThreshold=3   12 轮内没有出现大批量去重;共 49 次年轻代 GC
  AgeThreshold=10  12 轮内没有出现大批量去重;共 49 次年轻代 GC

身份与 intern

实测 · 本机执行输出
    去重之前:x == y ? false,x.equals(y) ? true
    去重之后:x == y ? false,x.equals(y) ? true,hashCode 相同 ? true
    intern 之后 i1 == i2 ? true

分步演示

字符串去重
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12 与 27,Apple Silicon;堆数字是 System.gc() 之后的 totalMemory−freeMemory
    • 没有验证:年龄阈值 3 与 10 在本实验里没有触发大批量去重的原因、去重线程的 CPU 占用、-XX:StringDeduplicationAgeThreshold 在 Full GC 里的作用、哈希表大小与内存开销、真实应用(如大型 JSON 解析)中的收益
    京ICP备2026067502号-1京公网安备11010502063487号