new string() 总是在堆上创建新对象,绕过字符串常量池,导致重复内容产生多个独立实例,引发内存膨胀、gc 频繁及 == 比较失败等问题;应避免滥用,优先使用字面量或已有字符串。

new String() 会绕过字符串池,每次都在堆上新建对象
调用 new String("abc") 不是从常量池取引用,而是强制在堆内存分配一个全新 String 实例——哪怕池里已有相同内容的字符串。这意味着:同一字面量反复 new 多次,就会产生多个独立对象,每个都占用堆空间,且无法被常量池自动复用。
常见错误现象包括:== 比较返回 false(即使内容一样),GC 日志中频繁出现短生命周期的 String 对象,Heap Dump 里看到大量重复内容的字符串实例。
- 每调用一次
new String("x"),至少新增一个对象(String实例)+ 一个底层char[](或byte[],取决于 JDK 版本) - 如果传入的是
new String(byteArr, charset),还会触发解码逻辑,额外生成临时缓冲区和异常处理开销 - 这些对象不会因为内容重复而自动去重,除非显式调用
.intern(),但该操作有同步开销,且在高并发下可能成为瓶颈
字符串池本身不缓解 new String() 的压力,反而可能掩盖问题
字符串池(StringTable)只对字面量、intern() 调用、部分编译期优化生效。它不拦截或重定向 new String() 的分配行为。所以“用了池”不等于“避免了堆膨胀”。更危险的是:开发人员误以为池能兜底,放松对 new String() 的审查。
使用场景中容易踩坑的典型例子:
- 从数据库读取字段后写成
new String(rs.getBytes("name")),而不是直接用rs.getString("name") - HTTP 解析时对 header 值反复做
new String(headerBytes, StandardCharsets.UTF_8),而没缓存解码结果 - 日志拼接中把
"[INFO] " + new String(data)当作“安全转换”,实则放大内存抖动
评估风险要盯住三类指标:对象数、存活时间、GC 行为
不能只看 process.memoryUsage() 或 Runtime.getRuntime().totalMemory() 这类粗粒度值。真正关键的是对象生成速率与回收效率的匹配程度。
- 用 JVM 参数
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps观察 Minor GC 频率:若每秒触发多次,说明 Eden 区被大量短命String快速填满 - 用
jstat -gc <pid></pid>查看S0C/S1C(Survivor 容量)和EC(Eden 容量)占比;若 Survivor 区持续堆积对象,说明部分String因引用未及时释放而晋升到老年代 - 用
jmap -histo <pid></pid>统计堆中java.lang.String和[C(char[])实例数量,再结合grep筛选高频重复内容(如固定前缀的日志字符串)
性能影响不是线性的:1000 次 new String("a") 可能只多占几 KB,但 10 万 QPS 下每请求执行 5 次,1 秒就新增 50 万个对象——Minor GC 间隔缩至毫秒级,CPU 花费在垃圾回收而非业务逻辑上。
替代方案不是“少用 new String”,而是“根本不需要 new String”
绝大多数场景下,new String() 是冗余操作。它的存在意义仅限于需要强制创建新对象(比如打破字符串共享、防止外部篡改底层数组),但这种需求在现代 Java 应用中极少见。
- 字面量直接用
"abc",复用常量池;构造器参数是已知字符串时,去掉new String(...)包裹 - 从
byte[]构造:若编码确定且字节数组来源可信,优先复用解码后的String缓存,或用StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString()(避免new String(bytes, cs)的防御性拷贝) - 工具方法中禁止出现
return new String(xxx),改为返回原始字符串或明确注释“此处 new 是为隔离可变底层”
最易被忽略的一点:JDBC 驱动、JSON 库、模板引擎内部可能已经做了字符串入池或复用,你在外面再套一层 new String(),等于主动放弃它们的优化成果。











