java 7起字符串常量池从永久代移至堆内存,intern()不再复制对象而直接存入引用,减少内存开销并支持gc;jdk 8取消永久代,常量池仍驻堆,行为不变。

Java 7 之前:字符串常量池在永久代(PermGen)
在 JDK 6 及更早版本中,String.intern() 方法会将字符串对象放入永久代(PermGen)中的字符串常量池。永久代是 JVM 堆外内存区域,专门用于存放类元数据、静态变量和字符串常量池。
此时调用 intern() 的行为是:
- 如果常量池中已存在该字符串(按 equals() 判断),则返回池中已有引用;
- 否则,将该字符串拷贝一份到永久代的常量池中,并返回新拷贝的引用。
这意味着即使堆中有一个字符串对象,intern() 也会在 PermGen 中新建一个副本,造成额外内存开销,且容易触发 java.lang.OutOfMemoryError: PermGen space。
Java 7:字符串常量池迁移至堆内存
JDK 7 开始,Oracle 将字符串常量池从永久代移到了Java 堆(Heap)中,这是重大变更。从此,intern() 不再复制字符串内容,而是直接将堆中首次出现的该字符串对象的引用存入常量池。
关键变化包括:
- 常量池本身成为堆中的一块逻辑区域(实际由 JVM 管理的哈希表结构,位于老年代附近);
- 调用 intern() 时,若池中无对应字符串,则把当前堆中字符串对象的引用加入池中,不创建新对象;
- 这显著减少内存占用,并使常量池可被垃圾回收器管理(只要没有强引用,池中条目可被清理)。
例如:
String s = new String("hello");<br>
String t = s.intern();<br>
System.out.println(s == t); // JDK 7+ 返回 false(s 是 new 出的对象)<br>
System.out.println(t == "hello"); // true("hello" 字面量已在堆常量池中)
Java 8 及以后:永久代彻底移除,元空间替代
JDK 8 彻底废弃永久代,引入元空间(Metaspace)存放类元数据,而字符串常量池仍保留在堆内存中,行为与 JDK 7 一致。
需要注意:
- intern() 的语义未变:仍是“首次出现则登记引用,否则复用”;
- 常量池大小不再受 -XX:MaxPermSize 限制,但受堆内存总量(-Xmx)约束;
- 可通过 -XX:StringTableSize 调整字符串表哈希桶数量(默认 60013),影响 intern 性能;
- 若大量调用 intern()(如解析大量唯一字符串),可能引发老年代频繁 GC 或 OOM(堆溢出),需监控 java.lang:type=StringPool MBean。
实用建议与注意事项
何时用 intern()?
- 处理大量重复字符串(如日志标签、配置键、解析 CSV 列名)且生命周期长,可节省堆内存;
- 需要字符串引用相等(==)而非内容相等(.equals())的场景(如状态机跳转、枚举模拟)。
慎用场景:
- 字符串内容高度随机或唯一(如 UUID、时间戳拼接),intern 无效且徒增 GC 压力;
- 在短生命周期方法中 intern 临时字符串,反而阻碍及时回收;
- JDK 7+ 中,new String("abc").intern() == "abc" 为 true,但 new String("abc").intern() == new String("abc") 仍为 false —— 切勿混淆对象身份与字面量归属。
验证方式:
- 使用 JConsole 或 VisualVM 查看 java.lang:type=StringPool 的 stringTableSize 和 numberOfEntries;
- 添加 JVM 参数 -XX:+PrintStringTableStatistics(JDK 8u20+)在退出时打印常量池统计。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











