# 5. 为什么 String.intern()
在 JDK 6 和 JDK 7+ 的行为不同?
# 标准答案
String.intern()
的行为变化主要是由于 运行时常量池(Runtime Constant Pool)的位置变化。在 JDK 6 及以前,常量池存储在 永久代(PermGen),空间有限,容易导致 OOM。而在 JDK 7 及以后,常量池被移动到 堆(Heap),这样 intern()
的字符串可以和普通 Java 对象共享内存,避免了 OOM,并提高了内存利用率。
# 答案解析
要理解 String.intern()
的行为变化,需要先了解 运行时常量池的存储位置。
# JDK 6 及以前:运行时常量池位于永久代(PermGen)
- 运行时常量池(包含
intern()
字符串)存储在 方法区(永久代)。 String.intern()
机制:- 如果字符串 已存在 在常量池中,返回已有的引用;
- 如果字符串 不存在,则 将其拷贝到常量池,并返回引用。
- 问题:
- 永久代大小受
-XX:MaxPermSize
限制,过多的intern()
调用容易导致OutOfMemoryError
。 intern()
会创建额外的字符串副本,导致额外的内存消耗。
- 永久代大小受
示例代码(JDK 6):
String s1 = new String("hello").intern();
String s2 = new String("hello").intern();
System.out.println(s1 == s2); // true
String s3 = new String("long_string_value").intern();
String s4 = new String("long_string_value").intern();
System.out.println(s3 == s4); // false(字符串过长,可能未存入常量池)
2
3
4
5
6
7
原因:
在 JDK 6,短字符串 可以存入永久代,但长字符串可能不会,导致 intern()
返回的引用不同。
# JDK 7+:运行时常量池迁移到堆(Heap)
- 常量池不再位于永久代,而是存放在堆内存(Heap)。
String.intern()
机制变化:- 如果字符串已存在,返回已有的引用;
- 如果字符串不存在,则 只存储字符串的引用,而不拷贝内容。
- 优化点:
- 由于常量池在堆中,
intern()
不会创建字符串副本,减少额外的内存占用。 - 运行时常量池大小不再受
MaxPermSize
限制,避免PermGen OOM
。
- 由于常量池在堆中,
示例代码(JDK 7+):
String s1 = new String("hello").intern();
String s2 = "hello";
System.out.println(s1 == s2); // true
String s3 = new String("long_string_value").intern();
String s4 = "long_string_value";
System.out.println(s3 == s4); // true(JDK 7+,长字符串也可存入常量池)
2
3
4
5
6
7
原因:
在 JDK 7+,intern()
只存储引用,不复制内容,因此长字符串也能正确返回相同引用。
# 常见错误与误区
误区 1:JDK 7+ 的 intern()
总是返回堆中的引用
错误:intern()
只是将字符串引用存入常量池,并不意味着字符串对象一定在堆中。
正确:如果字符串 原本就在常量池(如 "hello"
这种字面量),intern()
仍然返回常量池中的引用。
误区 2:JDK 6 和 JDK 7+ 的 intern()
行为完全相反
错误:intern()
的本质逻辑没变,仍然是查找已有字符串,或者存入池中。
正确:主要区别在于 存储位置变化(永久代 → 堆),以及 是否创建字符串副本。
误区 3:JDK 7+ intern()
解决了所有 OOM 问题
错误:虽然 intern()
避免了 PermGen OOM
,但如果堆过小,仍可能出现 Heap OOM
。
正确:应该监控 Heap
,合理配置 -Xmx
。
# 最佳实践
避免过度使用
intern()
intern()
适用于 大量重复字符串(如枚举、日志标签等)。- 不要对 短期字符串 使用
intern()
,否则可能占用过多堆空间。
合理配置堆内存
- 使用
-Xmx
适当增加堆大小,防止Heap OOM
。 - 结合
jmap -histo
查看 interned 字符串占用情况。
- 使用
监控字符串池使用情况
jcmd <pid> VM.stringtable
查看字符串池大小。jstat -gc <pid>
监控 GC 频率,防止intern()
影响 GC。
# 深入追问
为什么 JDK 6 intern()
可能导致 OOM,而 JDK 7+ 不会?
JDK 6 存储字符串副本 在 永久代(PermGen),空间有限,容易 OOM。JDK 7+ 只存储引用,并且常量池移动到了堆,避免 PermGen OOM
。
在 JDK 7+,如何高效管理 intern()
产生的字符串?
- 使用
WeakHashMap
维护缓存,避免过多intern()
导致堆膨胀。 - 监控 GC 日志,如果
intern()
造成 GC 频率过高,需要优化堆大小。
如何快速判断 intern()
是否生效?
- 运行
jcmd <pid> VM.stringtable
,查看interned strings
统计。 - 观察
s1.intern() == s2
结果是否为true
。
# 相关面试题
- JVM 运行时常量池的作用是什么?
- JDK 6 和 JDK 7+ 的
intern()
行为有何区别? - 为什么 JDK 7+ 运行时常量池存放在堆中?
- 如何优化
String.intern()
使用,防止 OOM? - 如何查看 Java 字符串常量池的内容?