Java String不可变性本身不直接导致内存泄漏,但不当使用(如JDK 6 substring共享数组、new String创建冗余对象、循环拼接滥用+、反射破坏内部状态)会引发内存占用高、GC压力大或数据一致性问题。

Java 中 String 的不可变性本身不会直接导致内存泄漏,但若配合不当的使用方式(尤其是旧版 JDK 行为或对象引用管理疏忽),可能引发间接的内存占用过高、GC 压力大,甚至被误认为“泄漏”。真正要防的,是那些因不可变性放大而暴露出来的资源滞留问题。
别让 substring 持有大数组(JDK 6 及更早已淘汰,但逻辑仍需理解)
JDK 6 中 substring 不复制字符数组,而是共享原 String 的 value[],仅调整 offset 和 count。这意味着:从一个 10MB 日志字符串中截取前 5 个字符,返回的小 String 仍强引用整个 10MB 数组,导致大数组无法回收。
- ✅ JDK 7u40 起已修复:substring 默认复制底层数组,彻底隔离大小对象生命周期
- ⚠️ 若仍在维护 JDK 6 环境,必须手动用 new String(sub) 强制复制,或改用 String.valueOf(chars, 0, len)
- ? 即便在新版本,对超长字符串做大量 substring 后又长期持有结果,也建议检查是否真需要保留全部原始上下文
避免常量池与堆对象混用造成冗余驻留
new String("abc") 会创建两个对象:一个在字符串常量池("abc" 字面量),一个在堆(new 出的实例)。后者若被意外长期引用(比如放进静态 Map 或缓存),就形成“本可复用却多占一份”的内存浪费。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- ✅ 绝大多数场景直接写 String s = "abc",由 JVM 自动入池并复用
- ✅ 需要显式控制池行为时,用 s.intern(),但注意 intern() 在 G1 GC 下可能触发额外开销,不建议高频调用
- ⚠️ 不要用 new String(s).intern() 来“强制入池”——它先造堆对象再入池,纯属画蛇添足
拼接不用 +,尤其不在循环里
String 不可变,每次 str += "x" 都新建对象。1000 次循环可能产生上千个中间 String 和 char[]/byte[],短生命周期对象虽能被 Minor GC 回收,但频繁分配会抬高 GC 频率,拖慢吞吐。
- ✅ 循环内拼接统一用 StringBuilder(单线程)或 StringBuffer(需线程安全)
- ✅ 初始化 StringBuilder 时预估容量,如 new StringBuilder(2048),减少内部数组扩容次数
- ⚠️ 不要在循环外声明 StringBuilder 却在循环内反复 new 它——那等于没优化
警惕反射或 Unsafe 破坏不可变性(极少见但后果严重)
虽然 String 类设计上杜绝了常规修改,但通过反射修改 value 字段或用 Unsafe 替换底层 byte[],会破坏 hashCode 缓存、使 equals 失效、干扰常量池语义,还可能导致 HashMap 查找不到 key 等诡异问题。
- ✅ 生产环境禁用反射修改 String 内部字段
- ✅ 不依赖非公开 API,不使用 sun.misc.Unsafe 操作 String 底层
- ⚠️ 这类操作一旦发生,不是内存泄漏,而是数据一致性崩溃,排查成本远高于内存问题
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










