string.intern()可将运行时字符串显式映射到jvm字符串常量池,若池中已存在相同内容(equals判断)则返回池中引用,否则加入并返回;适用于内容高度重复、生命周期长、堆内存压力大且内容可控的场景。

String.intern() 可以把运行时创建的字符串对象,**显式地映射到 JVM 字符串常量池中**,如果池中已存在相同内容的字符串(通过 equals() 判断),就返回池中的引用;否则,将该字符串加入常量池并返回其引用。这在大量重复字符串场景下能有效减少内存占用。
什么时候适合手动调用 intern()
不是所有字符串都该 intern —— 它有开销且需谨慎使用:
- 字符串内容高度重复(如解析日志、CSV 中的固定字段:城市名、状态码、协议名等)
- 单个字符串生命周期较长,且后续会被频繁比较或作为 key 使用(如 Map 的 key)
- 堆内存压力明显,且经排查发现大量重复的 String 实例占用了可观空间
- 字符串内容可控、长度适中(过长字符串 intern 后长期驻留常量池,反而不利于 GC)
intern() 的实际用法和关键细节
调用方式很简单,但行为依赖 JVM 版本和字符串来源:
-
new String("hello").intern()→ 返回常量池中 "hello" 的引用(JDK 7+ 后,常量池在堆中,所以这个引用指向的是堆内同一个对象) -
"hello".intern()→ 直接返回自身(字面量已入池) - 对
StringBuilder.toString()或String.substring()(JDK 7u6 之后)产生的字符串调用 intern(),也能去重 - 注意:intern() 是本地方法,有同步开销;高并发下频繁调用可能成为瓶颈
一个典型优化示例
假设你从数据库读取 10 万条订单记录,其中 status 字段只有 "PENDING"、"SHIPPED"、"DELIVERED" 三种值:
// 未优化:每个 new String(status) 都是独立对象
List<order> orders = resultSet.stream()
.map(row -> new Order(row.getId(), new String(row.getStatus()))) // ❌ 每次都新建
.collect(Collectors.toList());
// 优化后:统一指向常量池中的唯一实例
List<order> orders = resultSet.stream()
.map(row -> new Order(row.getId(), row.getStatus().intern())) // ✅ 复用
.collect(Collectors.toList());
</order></order>
这样,哪怕创建了 10 万个 Order,status 字段最多只占用 3 个字符串对象的内存(加上常量池开销)。
注意事项和潜在陷阱
intern 不是银弹,用错反而有害:
- 字符串内容不可控(如用户输入、随机 ID、时间戳)→ 绝对不要 intern,会导致常量池膨胀甚至 OOM
- JDK 6 中常量池在永久代,intern 过多容易触发 PermGen OOM;JDK 7+ 改到堆中,但仍受 -XX:StringTableSize 控制,默认 60013,冲突多时可适当调大
- 判断是否“相等”基于
equals(),不是 ==;所以不同编码、大小写、空格敏感的字符串不会被合并 - 无法主动清理常量池中的字符串,它们随类加载器存活,除非类卸载(极少见)










