String.intern()可显著减少低基数重复字符串内存占用,但需谨慎使用——它将字符串登记到堆中字符串常量池,受GC管理,扩容有成本,适合日志级别、配置键名等高频重复场景。

String.intern() 可以显著减少海量低基数重复字符串的内存占用,但需谨慎使用——它把字符串对象“登记”到 JVM 的字符串常量池(String Table),而这个池在 JDK 7+ 后位于堆内存中,不再是永久代(PermGen)。
字符串常量池现在就在堆里
JDK 7 起,字符串常量池从方法区(永久代)移到了 Java 堆中。这意味着:
- 调用
intern()产生的引用,指向的是堆中唯一的一个字符串实例; - 该实例和普通字符串一样受 GC 管理(只要没有强引用,就可能被回收);
- 池本身是一个哈希表(StringTable),底层是数组 + 链表/JDK8+红黑树,扩容成本存在,不能无限制 intern。
适合 intern 的典型场景
不是所有字符串都该 intern,关键是“低基数 + 高频重复 + 生命周期长”:
- 日志中的固定级别("INFO"、"ERROR"、"DEBUG");
- 配置项键名("timeout", "retries", "host");
- 枚举类 toString() 返回的字面量(若未重写,就是类名+点+值);
- 解析 CSV/JSON 时大量出现的字段名(如 "id", "name", "status")。
例如:100 万条日志中,每条含一个 "WARN" 字符串,不 intern 会创建 100 万个相同内容的 String 对象;调用 intern 后,实际只保留 1 个堆内实例,其余均指向它。
正确使用 intern 的方式
避免直接对任意字符串无脑调用 intern(),推荐模式如下:
- 只对已知有限集合的字符串做 intern(比如预定义常量、白名单字段);
- 读取外部数据后,先判断是否属于高频重复集,再决定是否 intern;
- 配合弱引用或缓存清理策略(如用
ConcurrentHashMap缓存 intern 结果,避免重复查表); - 注意:
"abc".intern()和new String("abc").intern()效果一致,但后者多分配一次临时对象,应尽量避免 new String + intern。
验证与监控建议
可通过 JVM 参数和工具确认效果:
- 启动时加
-XX:+PrintStringTableStatistics,JVM 退出时打印常量池大小、冲突数、平均链长; - 用 JFR 或 VisualVM 观察堆中字符串对象数量变化;
- 注意 StringTable 默认大小为 60013(质数),高并发 intern 可能引发哈希冲突,必要时用
-XX:StringTableSize=262144扩容(需是质数)。
不复杂但容易忽略:intern 是全局同步操作,频繁调用会成为瓶颈。低基数场景下,更稳妥的做法往往是提前构建不可变字符串池(如 static final Map











