string.intern()不是搬运字符串,而是登记引用并返回归属:先查常量池是否存在equals相等的字符串,存在则返回池中引用,否则将当前堆对象引用登记入池并返回;jdk 7+后仅存引用不复制内容,必须用返回值覆盖原变量才生效,适用于内容高度重复、来源分散且生命周期长的字符串,滥用会导致常量池膨胀和内存泄漏。

Java 中 String 的 intern() 方法不是“把字符串塞进池里”的搬运工,而是**登记引用 + 返回归属**的协调者。它不复制内容、不移动对象,只在字符串常量池中建立一条“这个值对应这个堆上对象”的映射记录,并返回该记录指向的引用。
intern 的实际行为(JDK 7+)
调用 str.intern() 时,JVM 做三件事:
- 查常量池:用
equals()比较池中是否已有内容相同的字符串引用; - 命中则返回:若存在,直接返回池中那个已有引用;
- 未命中则登记:若不存在,就把当前
str对象本身的引用(注意:是原堆对象,非副本)加入池,并返回这个刚登记的引用。
也就是说,intern() 后你拿到的,一定是常量池“认领过”的那个引用——它可能来自字面量,也可能来自你刚刚传入的堆对象。
怎么让一个动态字符串真正“入池”并生效
关键不在“调用”,而在“替换引用”。很多开发者调了 .intern() 却没效果,是因为仍用着原来的堆引用。正确做法是立即用返回值覆盖原变量:
-
String heapStr = new String("city");→ 堆中新对象,与字面量"city"不同引用 -
heapStr = heapStr.intern();→ 登记后拿到池中引用,赋值给heapStr -
String lit = "city";→ 字面量自动从池取 System.out.println(heapStr == lit); // true
不覆盖引用,就等于白调:原 heapStr 还指着堆对象,== 判断依然为 false。
哪些字符串适合手动 intern
不是所有字符串都该进池。真正值得干预的,需同时满足:
- 内容高度重复:如数据库查出的
"ACTIVE"、日志中的"WARN"、HTTP 头里的"application/json",十万条记录中只出现几十个不同值; - 来源分散且非字面量:由
ResultSet.getString()、JSON.parse()、substring()等生成,无法靠编译期入池; - 生命周期长、复用频繁:作为 Map key、状态字段、权限标识等,长期存活且高频参与
==或哈希计算。
反例:用户昵称、UUID、时间戳拼接串、正则匹配结果子串——这些唯一性强、不可控、临时性高,一调 intern() 就是往常量池里堆垃圾。
使用时必须注意的风险点
intern() 是同步方法,底层操作字符串表(哈希结构),有真实开销和隐患:
- 默认常量池大小仅 60013 槽位(
-XX:StringTableSize),冲突多或扩容时性能明显下降; - 池中字符串被 JVM 强引用,只要类没卸载、JVM 没退出,就无法被 GC 回收;
- 对随机输入无防护调用,极易导致元空间(Metaspace)持续增长,最终
OutOfMemoryError: Metaspace; - 行为依赖类加载顺序:若某字面量尚未加载,首次
intern()会成功登记;之后再出现同字面量才命中——逻辑可能“有时成立、有时不成立”。
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











