String.intern()核心变化是常量池位置迁移:JDK 6在永久代,需复制字符串导致OOM风险;JDK 7+移至堆中,直接存引用实现对象复用,避免复制与PermGen OOM。

String.intern() 方法的行为,核心变化在于字符串常量池(String Table)的内存位置迁移,而非方法语义本身。它始终是将字符串对象“登记”到常量池中,并返回池中已存在或新加入的引用;真正影响性能和GC行为的关键,是这个池子“放在哪”。
JDK 6:常量池在永久代(PermGen)
永久代是堆外的一块固定大小内存区域,用于存放类元数据、静态变量和字符串常量池。此时调用 intern(),若池中无该字符串,则会把堆中的字符串对象本身复制一份到永久代(注意:是复制!不是引用)。这导致两个问题:
- 永久代空间有限,大量 intern 操作容易触发 java.lang.OutOfMemoryError: PermGen space
- 堆中原始对象仍存在,复制后的字符串独立存活,造成冗余和内存浪费
JDK 7:常量池迁移到堆内存(Heap)
从 JDK 7 开始(确切说是 7u40 起稳定),字符串常量池被移到了Java 堆中。这是重大优化:
- 不再复制对象:如果池中不存在,intern() 直接将堆中已有字符串对象的引用存入常量池
- 常量池随堆动态扩展,避免 PermGen OOM;GC 可正常回收未被引用的 intern 字符串
- 实际效果是:堆中一个字符串对象,可能同时被普通变量和常量池引用,但只有一份实例
JDK 8 及以后:元空间替代永久代,常量池仍在堆中
JDK 8 彻底移除了永久代,引入本地内存的元空间(Metaspace)存放类元数据;而字符串常量池保持位于堆内,行为与 JDK 7 一致。唯一区别是:
立即学习“Java免费学习笔记(深入)”;
- 元空间 OOM 错误变为 java.lang.OutOfMemoryError: Metaspace,但和 intern() 无关
- 字符串常量池的容量可通过 -XX:StringTableSize 参数调整(默认约 60013 个桶),影响哈希查找效率
一个典型对比示例
以下代码在不同 JDK 行为不同:
String s = new String("hello"); // 在堆中新建对象
String t = s.intern(); // 触发 intern
System.out.println(s == t); // JDK 6 → false;JDK 7/8 → true
原因:JDK 6 中 t 指向永久代副本,s 指向堆原对象;JDK 7+ 中 t 和 s 指向堆中同一个对象(因字面量 "hello" 已在编译期进入常量池,s.intern() 返回的就是那个引用)。


















