String不可变性本身不是问题,但会引发对象爆炸、重复驻留、错误复用等内存问题;需用jmap/VisualVM查String实例数、char[]/byte[]占比及大小,重点排查循环拼接、new String()、substring共享数组及误用intern等高危模式。

String 不可变性本身不是问题,但它会让“看似轻量”的操作悄悄吃掉大量内存。排查内存占用过高,关键不是怀疑不可变性,而是顺着它引发的典型行为去定位:对象爆炸、重复驻留、错误复用。
看堆中 String 实例数量和 char[]/byte[] 占比
用 jmap 或 VisualVM 导出堆快照(heap dump),重点观察:
- String 对象总数是否远超业务预期(比如 50 万+)
- char[](JDK8 及以前)或 byte[](JDK9+)是否排在内存占用 Top 3,且平均长度大、重复率高
- 是否存在大量短生命周期 String(如日志拼接、SQL 构建中间串),但没被及时回收
查高频创建点:循环内拼接 + new String() 调用
搜索代码中这些高危模式:
- for / while 循环里写 result += "xxx" → 每次生成新 String + 新数组
- new String(rs.getString("name")) 或 new String(buffer) → 绕过常量池,强制堆分配
- String.format(...) 在循环内反复调用 → 内部仍走 StringBuilder,但额外有参数解析开销
验常量池滥用:intern() 是否在不该出现的地方狂刷
不是所有字符串都适合 intern。重点检查:
立即学习“Java免费学习笔记(深入)”;
- 分页查询中,每行都对 rs.getString("id") 或时间戳字段调用 .intern()
- 日志语句拼完再 log.info(msg.intern()) → 日志内容基本唯一,入池后长期占堆又无复用
- UUID、随机 token、毫秒级时间字符串等明显唯一值被批量 intern
盯 substring 截取逻辑(尤其运行在 JDK6/7 的老系统)
如果堆里存在大量小字符串,但它们的 value 数组却异常巨大(比如 1MB),很可能是旧版 substring 共享底层数组导致:
- JDK6:substring 返回对象仍强引用原大数组,无法 GC
- JDK7+ 已修复,但若代码里手动用了反射或自定义截取工具,仍可能复现类似问题
- 安全写法:new String(str.substring(start, end)) 强制复制字符


















