String.intern()仅做内容归一化,不自动去重;需识别高重复、长生命周期字符串(如状态码、HTTP方法)谨慎调用,避免对UUID等唯一字符串使用;推荐ConcurrentHashMap显式缓存替代,以规避全局锁与回收限制。

直接用 String.intern() 并不能自动“去重变量”,它只对字符串内容做引用归一化。真正减少冗余内存,关键在识别哪些字符串值得池化、怎么安全调用、以及何时该换更可控的方案。
先搞清:常量池不是万能回收站
调用 str.intern() 不会删掉原字符串对象,只是让 JVM 记住“这个内容已有代表”。原对象仍留在堆里,等 GC 回收。所以效果取决于两点:
- 原始字符串是否大量重复(比如 10 万次读到 "PENDING")
- 这些字符串是否由
new String()、substring()(JDK 7u6 前)、StringBuilder.toString()等方式创建——这类对象不会自动进池,容易堆积
而 "abc".intern() 没意义,字面量本身已在池中;对 UUID、时间戳、随机 token 调用,只会白占哈希槽、拖慢并发性能。
适合手动 intern 的典型场景
满足“高重复 + 长生命周期 + 内容可控”三条件时,收益明显:
- 从 JSON/CSV/数据库读取的状态字段:如
"SUCCESS"、"FAILED"、"SHIPPED" - 日志解析提取的固定值:如 HTTP 方法
"GET"、"POST",日志级别"WARN"、"ERROR" - 反射获取的方法名、字段名:
method.getName().intern()(避免每次反射都新建字符串)
示例写法(带空值防护):
String status = row.getString("status");status = (status != null) ? status.intern() : null;
比 intern 更稳、更灵活的替代方案
当你要控制生命周期、规避全局锁、或处理复杂逻辑时,显式缓存更可靠:
- 用
ConcurrentHashMap<string string></string>自建池:可按业务批次清理(比如一次导入完成后 clear),不污染 JVM 全局字符串表 - Go 或 Python 等语言无内置 intern,直接用
map[string]string或sys.intern即可,语义清晰、无版本差异 - 对极短字符串(如
"Y"、"N"、"404")或超长字符串(>1KB),intern 收益低甚至负向,建议加长度判断跳过
验证有没有真省下内存
别猜,用工具看:
- 启动 JVM 加参数:
-XX:+UseG1GC -Xlog:gc*:gc.log:time,对比前后 GC 频率 - 用
jmap -histo <pid></pid>查看java.lang.String实例数与 retained heap 变化 - 用 VisualVM / JFR 抓堆快照,过滤出重复内容的字符串,确认它们是否已共享引用
如果实例数没降、或者常量池占用堆增长明显,说明用错了对象或重复率不够。

















