Java String不可变性本身无害,问题在于误操作导致内存暴涨:循环拼接应改用预设容量的StringBuilder;避免new String("xxx");JDK7+ substring已安全但仍有复制开销;字面量自动入常量池且JDK9+启用紧凑字符串节省内存。

Java 中 String 不可变性本身不是问题,问题出在“误以为能修改它”——每次拼接、截取、替换,其实都在堆上悄悄新建对象。反复操作不加节制,内存占用会指数级上升,GC 频繁,甚至触发 Full GC。避开暴涨,关键是识别高危场景,并用对工具。
循环内拼接:最隐蔽的内存炸弹
写 str += "x" 看似简洁,但每次执行都生成一个新 String + 一个临时 StringBuilder(JDK 7+ 底层自动创建)。10 万次循环,就是 10 万个 String 实例 + 10 万个 StringBuilder 对象,大量 char[] 或 byte[] 数组堆积在堆中。
- ✅ 正确做法:循环外声明 StringBuilder,循环内只调用
append();多线程共用才换 StringBuffer - ⚠️ 关键细节:初始化时预估容量,如
new StringBuilder(2048),避免内部数组反复扩容复制 - ? 小提醒:编译器会对形如
"a" + "b" + "c"这类静态拼接自动优化为单个字面量,无需干预
new String("xxx"):凭空多造一个对象
写 new String("hello"),JVM 先从常量池取出 "hello",再在堆里 new 一份一模一样的副本。结果是:1 个常量池对象 + 1 个完全冗余的堆对象。如果代码中批量调用,等于主动给 GC 加压。
- ✅ 绝大多数情况直接用字面量:
String s = "hello",JVM 自动复用常量池实例 - ⚠️
intern()不是补救手段:对已存在的字面量调用"hello".intern()没效果,纯属多余 - ? 特殊用途才 new:比如需要确保与常量池对象地址不同(极少见),或配合反射做某些底层测试
substring 和 split:旧版陷阱已修复,新版开销更透明
JDK 6 及以前,substring 不复制字符数组,只是共享原 value[] 并调整偏移量。一个 5MB 日志字符串取前 10 字符,返回的小字符串仍持有整个 5MB 数组引用,导致大数组无法回收。
立即学习“Java免费学习笔记(深入)”;
- ✅ JDK 7u6 起已改为独立拷贝,安全但开销明确:每次调用都触发一次数组复制
- ⚠️ 大文本频繁切片(如日志解析)仍需警惕:考虑用
CharBuffer.wrap()或直接操作索引,避免无谓复制 - ?
split()本质是多次substring+new String,长字符串配复杂正则时,建议预编译Pattern并复用Matcher
常量池和紧凑字符串:免费的内存红利
字符串字面量自动入池,这是 JVM 白送的复用机制。JDK 9+ 更进一步:纯 ASCII 字符串改用 byte[] 存储,内存直接减半,且完全透明,无需改代码。
- ✅ 配置项、HTTP 方法、状态码等固定值,一律用双引号字面量,例如
String method = "POST" - ⚠️ 运行期读取的字符串(如数据库字段)慎用
intern():重复率低的 UUID、时间戳等调用后白占元空间,还带同步开销 - ? 验证是否启用紧凑字符串:可用 JOL 工具查看对象内存布局,确认
value是byte[]且coder值为 0(Latin-1)


















