绝大多数情况下String.intern()不能省内存,反而拖慢性能、增加GC压力甚至引发Metaspace OOM;仅在内容高度重复、来源分散且长期存活的字符串场景下才可能有效。
String.intern() 真的能帮你省内存吗?先看结论
绝大多数情况下,
不仅不能省内存,反而会拖慢性能、增加 GC 压力,甚至引发
(Java 8+)或永久代溢出(Java 7)。它只在极少数明确控制字符串生命周期、且重复率极高、长度适中的场景下才值得考虑。
什么时候调用 intern() 才可能有效?
核心前提:你有一批**内容高度重复、来源分散(如 JSON 解析、数据库字段、HTTP 头值)、且长期存活**的字符串,而 JVM 默认字符串常量池无法覆盖这些运行时生成的实例。
典型场景:解析数万条日志,其中
、
、
出现频次超千次,且这些字符串由
或
(Java 7u6 之前)等非池化方式创建
必须配合弱引用/软引用缓存或显式生命周期管理——
后的字符串会驻留在元空间,直到类卸载或 JVM 退出
字符串长度建议 ≤ 100 字符;过长字符串 intern 后对内存节省微乎其微,但元空间占用固定且不可回收
为什么直接对所有字符串调用 intern() 很危险?
不是“优化开关”,而是把字符串对象从堆挪到元空间(Java 8+)的全局哈希表中。这个表默认大小极小(
),且扩容代价高、不可控。
频繁调用会导致元空间快速耗尽,抛出
每次
都要加全局锁(Java 8 中已优化为分段锁,但仍存在竞争),高并发下成为瓶颈
GC 无法回收 interned 字符串,即使原堆引用已消失——它们被元空间的内部表强引用着
与字符串拼接、正则匹配等操作混用时,极易误将临时字符串 intern,例如:
会把无意义子串塞进池里
如果真要上,该怎么写才相对安全?
别裸调
。用带阈值和缓存的包装逻辑,且必须限制作用域。
Eclipse导入Android或其他的JAVA项目的正确方法 WORD版
本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
下载
立即学习
“
Java免费学习笔记(深入)
”;
关键点:
统计频次而非盲目 intern——避免把只出现 1~2 次的字符串污染元空间
长度过滤:排除太短(本身开销小)和太长(intern 效益低、元空间浪费大)的字符串
不要在循环内无条件调用
,尤其不要对
这类临时结果操作
上线前务必用
和
观察元空间增长趋势
真正难的不是怎么调用
,而是证明它在你的具体数据分布和 GC 策略下确实降低了整体内存 footprint——这需要压测对比,而不是凭直觉加一行代码。
String.intern()OutOfMemoryError: Metaspace"ERROR""user_id""2024-01-01"new String(...)substring()intern()String.intern()-XX:StringTableSize=60013OutOfMemoryError: Metaspaceintern()str.substring(0, 3).intern()intern()public class SafeStringPool {
private static final int MIN_LENGTH = 2;
private static final int MAX_LENGTH = 64;
private static final int MIN_REPEAT_THRESHOLD = 100;
// 用 ConcurrentHashMap 统计频次,避免全局锁
private static final ConcurrentHashMap counter = new ConcurrentHashMap<>();
public static String pool(String s) {
if (s == null || s.length() < MIN_LENGTH || s.length() > MAX_LENGTH) {
return s;
}
LongAdder c = counter.computeIfAbsent(s, k -> new LongAdder());
c.increment();
if (c.longValue() >= MIN_REPEAT_THRESHOLD) {
return s.intern(); // 到阈值才真正 intern
}
return s;
}
} intern()StringBuilder.toString().intern()jstat -gc jcmd VM.native_memory summary intern()