String.intern()通过查StringTable复用堆中已有字符串对象,JDK7+后常量池在堆内存,不复制新对象;仅对动态创建的重复字符串省内存,需防内存泄漏和哈希冲突。

String.intern() 是怎么把字符串塞进常量池的
它不是“复制一份到常量池”,而是查表:如果常量池里已有内容相同的 String 对象,就直接返回那个引用;没有就将当前字符串对象(注意:是对象本身,不是新拷贝)放入常量池并返回它。
关键点在于:JDK 7+ 后,常量池在堆内存里,intern() 可以复用已有的堆对象,不是创建新对象。
- 只对
String对象生效,对字面量(如"abc")调用没意义——它们本来就在池里 - 返回的是常量池中对应字符串的引用,可能和原对象是同一个,也可能不是(取决于是否已存在)
- 底层用的是一个类似哈希表的结构(
StringTable),查找有开销,别在热循环里狂调
什么时候调用 intern() 真能省内存
典型场景是大量重复字符串来自外部输入(比如解析 CSV、日志行、HTTP header 值),且生命周期长、后续要频繁比较或作为 Map key。
比如读取百万行日志,每行有个 status 字段只有 "OK"、"ERROR"、"TIMEOUT" 几种值,原始解析出来是独立的 String 对象,这时对每个 status 调一次 intern(),能让所有相同状态共享一个对象。
立即学习“Java免费学习笔记(深入)”;
- 必须保证这些字符串确实高度重复,否则白费哈希查找时间,还往常量池塞垃圾
- 注意 GC:常量池里的对象只要被引用就一直活,滥用可能导致内存泄漏(尤其和 ClassLoader 绑定时)
-
new String("abc").intern() == "abc"是 true;但new String("abc").intern() == new String("abc")是 false —— 别误以为能“统一对象身份”
为什么 String.intern() 有时不生效(== 比较还是 false)
最常见原因是:你调用 intern() 的对象,其内容在常量池里已经被另一个不同来源的字符串占用了,而那个字符串和你当前对象的字符数组内容虽然相同,但所属类加载器不同,或者 JVM 参数限制了常量池大小,导致冲突或淘汰。
- JVM 启动参数
-XX:StringTableSize=60013影响哈希桶数量,太小会增加碰撞,间接影响intern()效率甚至行为(极少数版本有 bug) - 使用了自定义 ClassLoader 加载的类生成的字符串,
intern()后可能无法和系统类加载器下的同内容字面量等价(JDK 8u40+ 修复较多,但仍需验证) - 字符串含不可见字符(BOM、零宽空格)、编码不一致(如 UTF-8 vs ISO-8859-1 解析出的 byte[] 再转 String)会导致内容实际不同,
intern()当然不匹配
替代 intern() 的更安全做法
除非你明确需要通过 == 判断相等,或者压测证明 intern() 带来显著收益,否则优先用 .equals() + 缓存(如 ConcurrentHashMap<string string></string>)更可控。
- 自己维护一个弱引用缓存:
Map<string weakreference>></string>,避免阻塞 GC - 用
String canonicalize = cache.computeIfAbsent(s, k -> k)替代s.intern(),逻辑清晰、可调试、不依赖 JVM 实现细节 - 构建阶段(如配置加载)就做归一化,运行时不再动态
intern()—— 避免并发竞争StringTable锁(虽然 JDK 8 已分段锁,但仍有成本)
常量池优化不是银弹。真正卡内存时,先用 jmap/jstat 确认 StringTable 是否真膨胀,再决定要不要动 intern()。很多人调了发现没变化,是因为问题根本不在字符串去重上。


















