string.intern() 返回常量池中已存在字符串的引用,内容相同即复用;若不存在则将当前字符串内容放入池并返回其引用,与是否为字面量无关。

String.intern() 什么时候会返回堆上对象的引用
它不总返回常量池里的“新”对象,而是返回池中已存在字符串的引用;如果不存在,才把当前字符串(注意:是当前 String 对象的字符内容)放入池,并返回该引用。关键在于“内容相同即复用”,和你调用的是不是字面量无关。
常见错误现象:"abc".intern() == "abc" 是 true,但 new String("abc").intern() == new String("abc") 是
false</false> —— 因为右边是堆上新对象,左边是常量池引用,两者地址不同。</p> <ul> <li>使用场景:避免重复字符串占用内存(如解析大量 JSON 字段名、日志中的固定标签)</li> <li>参数差异:无参数,纯实例方法,对 <code>null调用会抛
NullPointerException
JDK 6 和 JDK 7+ 的 intern() 行为差异
JDK 6 把字符串常量池放在永久代(PermGen),空间小且不可扩容;JDK 7 起移到 Java 堆,受 GC 管理,行为更可控。这意味着在 JDK 6 中滥用 intern() 容易触发 java.lang.OutOfMemoryError: PermGen space,而 JDK 7+ 更多是堆内存压力。
典型误判:以为 intern() 总能“省内存”,其实它只是把字符串从堆某处挪到常量池——如果原对象还被强引用着,反而多占一份。
立即学习“Java免费学习笔记(深入)”;
- 必须确认字符串生命周期:长期存活、高复用率的才适合
intern() - JDK 8u20+ 默认开启
-XX:+UseG1GC后,常量池回收更积极,但依然不会回收仍被引用的 interned 字符串 - 验证方式:用
jmap -histo:live <pid></pid>查看java.lang.String实例数,配合jstat -gc <pid></pid>观察老年代变化
为什么 String.valueOf(x).intern() 不等于 x.toString().intern()
因为 String.valueOf(null) 返回的是字符串 "null",而 null.toString() 直接抛 NullPointerException。哪怕 x 是非 null 对象,valueOf 可能调用 toString(),但也可能走分支逻辑(比如 String.valueOf(char[]) 直接构造新字符串)。
容易踩的坑:用 intern() 做 key 归一化时,没统一入口,导致相同语义的字符串进了两次常量池。
- 永远优先用
String.valueOf(x)替代x.toString()做空安全转换 - 若 x 是
Integer等包装类,String.valueOf(42)和42 + ""都会生成新对象,再intern()才进池 - 数字转字符串后
intern()效果有限:除非大量重复数值(如状态码"200"、"404"),否则性价比低
用 intern() 做字符串比较前,先问自己三个问题
它不是万能的“加速器”。很多场景下,用 equals() 或 ==(仅限确定是字面量或已 intern 过)更直接可靠。
- 这个字符串是否真的会在整个应用生命周期里高频出现?(比如配置项 key、协议字段名)
- 有没有其他线程同时调用
intern()?高并发下锁竞争会让它变慢 - 你能否控制所有创建路径?一旦漏掉某个构造方式(比如用了
StringBuilder.toString()却没intern()),比较就会失效
最常被忽略的一点:常量池里的字符串,只要被任何地方强引用着,就不会被 GC;而堆上字符串可以随时回收。所以“省内存”是有代价的——换来的可能是更难排查的内存泄漏。










