String.intern()仅在高重复率的动态字符串场景下省内存,JDK7+后常量池移至堆中,避免跨区拷贝;但滥用会导致字符串表膨胀、哈希冲突或锁竞争,需结合监控谨慎使用。

String.intern() 真的能帮你省内存吗?先看实际效果
不能一概而论。只有当字符串内容重复率高、且原始对象来自 new String() 或非字面量拼接(如 StringBuilder.toString())时,intern() 才可能降低堆内存占用;对已存在于常量池的字面量(如 "hello")调用它,只是返回池中引用,无副作用。
关键判断点:用 jmap -histo 或 VisualVM 观察堆中 java.lang.String 实例数和总大小,再对比开启 intern() 后的变化——别靠直觉。
哪些场景调用 intern() 有意义
常见有效场景包括:
- 从数据库/JSON/CSV 读取大量重复字符串 ID(如用户状态
"active"、"inactive"),且后续需频繁 equals 判等 - 解析日志行时提取固定字段值(如 HTTP 方法
"GET"、"POST"),每次 new 出来又不复用 - 反射获取的类名、方法名(
Method.getName()返回的是运行时构造的字符串,非字面量)
反例:用 "abc".intern() —— 字面量本身已在常量池,多此一举;或对随机 UUID 字符串调用 —— 几乎无重复,反而增加 JVM 字符串表压力。
intern() 的行为差异:JDK 6 vs JDK 7+
JDK 6 中,常量池在永久代(PermGen),intern() 会把字符串拷贝进 PermGen;JDK 7+ 后,常量池移到堆中,intern() 只是确保堆内唯一引用,不再触发跨区域拷贝。
这意味着:
- JDK 7+ 下,
intern()开销显著降低,但要注意:如果字符串本身已在堆中且很长,intern()不会释放原对象,只是让池指向它——原对象仍需 GC 回收 - 默认字符串表大小为 1009(质数),高并发调用
intern()可能引发哈希冲突,拖慢性能;可通过-XX:StringTableSize=60013调大(需重启 JVM) -
intern()是同步方法,极端高频调用可能成为锁竞争点
怎么安全地用 intern() 避开典型坑
直接裸调 str.intern() 很危险,尤其 str 为 null 时抛 NullPointerException;更隐蔽的问题是语义混淆:你本想复用字符串,却意外改变了其他逻辑对同一字面量的引用判断。
实操建议:
- 总是判空:
str != null ? str.intern() : null - 避免在
equals()或hashCode()内部调用intern()—— 容易引发不可预测的锁或 GC 行为 - 不要依赖
==判断 intern 后的字符串相等性,除非你 100% 控制所有来源(比如全链路都强制 intern),否则混用字面量和非字面量会导致==失效 - 批量处理时,优先考虑构建
Map<string string></string>做显式缓存(如cache.computeIfAbsent(str, String::intern)),比无条件调用更可控
最常被忽略的一点:intern() 不解决字符串内容本身冗余(比如超长日志消息),它只解决“相同内容的多个 String 对象”问题。真要减内存,得先压缩内容、再考虑引用去重。

















