String.intern()操作JVM运行时常量池(JDK7+位于堆内存,以StringTable哈希表实现),仅存储字符串引用而非复制对象;JDK6在永久代复制内容,JDK7+起优化为引用登记,JDK17+支持GC回收无强引用条目。

String.intern() 的底层内存分配原理,核心在于它操作的是 JVM 运行时常量池(Runtime Constant Pool),而这个池在 JDK 7 及以后已完全迁移至堆内存中,并以哈希表(StringTable)结构组织。
intern() 操作的是运行时常量池,不是编译期常量池
很多人误以为 intern() 是往 .class 文件里的编译期常量池里放东西,其实不是。它操作的是 JVM 加载类后动态维护的运行时常量池——一个逻辑上属于方法区、但物理上位于堆内存的区域(JDK 7+)。这个池由 JVM 私有管理,只存字符串引用(不是复制对象),不存完整对象副本。
底层用 StringTable 哈希表实现,性能受参数影响
StringTable 是 JVM 内部的一个固定大小哈希表,默认容量为 60013(可通过 -XX:StringTableSize=N 调整)。每次调用 intern(),JVM 会按字符串内容计算 hash 值,查找或插入该表:
- 若命中(即已有相同内容的字符串引用),直接返回池中已有引用;
- 若未命中,则将当前字符串对象的引用存入表中对应槽位,并返回该引用。
注意:这里“存入”的是堆中已存在的字符串对象的引用,不是新建对象,也不是深拷贝内容(JDK 7+ 的关键优化)。
立即学习“Java免费学习笔记(深入)”;
不同 JDK 版本的内存行为差异明显
JDK 版本直接影响 intern() 的内存动作逻辑:
- JDK 6 及之前:常量池在永久代(PermGen),intern() 首次遇到字符串时,会把堆中字符串的内容复制一份到永久代,返回新复制对象的引用;
- JDK 7/8/9+:常量池移到堆内存,intern() 不复制对象,只记录堆中对象的引用;如果该字符串字面量(如 "abc")已在代码中出现过,那么 intern() 直接返回字面量对应的堆内对象引用;
- JDK 17+(默认开启 ZGC/Shenandoah 等):StringTable 仍位于堆,但 GC 可回收其中无强引用的条目,避免内存泄漏风险。
引用统一性与 GC 可达性需同时考虑
调用 intern() 后,返回的字符串引用指向常量池中的条目,而该条目本身只是堆中某个字符串对象的引用。因此:
- 只要堆中那个字符串对象没被其他变量强引用,又没被常量池条目引用,它就可能被 GC 回收;
- 但一旦被 intern() 注册进 StringTable,该条目本身会被 JVM 强持有(直到显式清理或 JVM 退出),所以 intern() 过的对象通常不会被轻易回收;
- 滥用 intern()(比如对大量动态生成的长字符串反复调用)会导致 StringTable 膨胀,引发哈希冲突加剧、查找变慢,甚至触发 Full GC。


















