String.intern()是登记+复用机制:查常量池有无相同字符串,有则返回其引用,无则登记当前堆中字符串的引用并返回;JDK 7+后池在堆中,不复制不移动对象。

String.intern() 并不是“强行把堆里字符串滑入常量池”的搬运工,而是一个**登记+复用**机制:它查常量池有没有内容相同的字符串,有就返回那个引用;没有,就把当前堆中字符串的引用记进池里,然后返回这个引用。JDK 7+ 后池就在堆里,所以根本不存在“跨区域滑动”,只是加了一条指向已有对象的登记记录。
intern 的真实行为:查重登记,不复制不移动
调用 s.intern() 时:
- JVM 检查字符串常量池中是否存在 equals(s) 为 true 的字符串
- 若存在,直接返回池中那个字符串的引用(原堆对象不受影响)
- 若不存在,将 s 的引用(不是副本!)加入常量池,并返回该引用
- 原变量 s 仍指向堆中对象——想复用池中引用,必须显式赋值:s = s.intern();
什么时候能“让堆字符串进池”?关键看池里有没有
效果取决于常量池当前状态:
-
字面量已存在:如
String s = new String("ok"); s.intern();→ 返回字面量"ok"的引用,但 s 本身未变,s == "ok"仍是 false -
池里真没有:如
String s = new StringBuilder().append("ab").append("c").toString(); s.intern();→ JDK 7+ 下,s 的引用被登记进池,后续"abc"就和它 == - 注意:拼接产生的字符串(非字面量)在编译期不会自动入池,正是 intern 的典型适用点
安全使用的三步实操
别裸调,要闭环:
-
先判空:避免
null.intern()抛 NPE —— 写成s != null ? s.intern() : null -
立即赋值:
s = s.intern();,否则原引用仍指向堆对象,失去归一化意义 - 限场景用:只对高重复、来源分散、生命周期长的字符串(如日志字段、数据库枚举值)启用;随机ID、临时子串、超长文本一律跳过
性能与风险提醒
intern 是同步方法,底层依赖哈希表(默认大小 1009):
- 高频调用易引发锁竞争或哈希冲突,拖慢吞吐
- intern 后的字符串被 JVM 强引用,GC 无法回收,长期驻留——不是“省内存”,而是“换地方存”
- 可通过
-XX:StringTableSize=60013扩容表大小(需重启 JVM),但不能滥用 - 永远不要在
equals()、hashCode()或正则匹配中间接调用它

















