string.intern()仅在字符串动态创建、内容重复且属有限集合时才有效省内存;需避字面量抢占、控长度、防空指针,并用工具验证效果,否则易增开销。

String.intern() 能减少变量内存重复占用,但只在特定条件下有效:字符串内容高度重复、来源为运行时动态创建(非字面量)、生命周期长且可控。它不是通用压缩工具,用错反而增加开销。
哪些字符串调用 intern 才真能省内存
只有满足以下全部条件时,intern 才会显著降低堆中 String 实例数量:
- 字符串由外部输入生成,比如
resultSet.getString("status")、jsonNode.get("type").asText()、new StringBuilder().append(...).toString() - 内容属于有限集合,例如状态值("ACTIVE"/"INACTIVE")、HTTP 方法("GET"/"POST")、城市名、协议名等
- 原始字符串未被字面量“抢先注册”——若代码里已写过
"PENDING",再对数据库读出的"PENDING".intern()就只是返回已有引用,不新增对象,但也不会回收原堆对象 - 字符串长度适中(一般建议 ≤ 1KB),避免大字符串长期驻留常量池,干扰 GC
怎么写才安全又高效
裸调 str.intern() 风险高。推荐按场景分层处理:
- 判空保护:
str != null ? str.intern() : null,防止NullPointerException - 高频固定集优先预加载:
static final Map<string string> KNOWN_KEYS = Map.of("id", "id".intern(), "name", "name".intern());</string>,后续直接查表复用,绕过同步锁 - 批量解析时集中处理:如 JSON key 解析,用
KeyDeserializer或自定义TypeAdapter拦截并 intern,不逐个无脑调用 - 避免
new String("xxx").intern():多分配一次堆对象,纯属浪费;应直接用字面量或确保源头是动态构造
验证是否真的起作用
别靠猜测,用工具确认效果:
- 启动加参数:
-XX:+PrintStringTableStatistics,JVM 退出时输出常量池大小、冲突数、平均链长 - 对比堆快照:
jmap -histo:live <pid></pid>查看java.lang.String实例数和总大小,开启 intern 前后做差值 - 用 == 判断是否共享:
String a = "SHIPPED"; String b = dbRow.getStatus().intern(); System.out.println(a == b); // true 表示成功复用 - 注意陷阱:若返回 false,可能是字符串含不可见字符(如 BOM、零宽空格)、或 JVM 字符串表太小导致哈希冲突丢弃(可调
-XX:StringTableSize=262144)
比 intern 更稳的替代方案
对于低基数、强可控场景,显式字符串池往往更可靠:
- 用
ConcurrentHashMap<string string></string>缓存 intern 结果,避免重复查表和锁竞争 - 构建静态不可变池:
private static final Map<string string> STATUS_POOL = Map.of("PENDING", "PENDING", "SHIPPED", "SHIPPED");</string>,业务层统一通过STATUS_POOL.get(status)获取规范实例 - 配合弱引用或定时清理策略,防止长期驻留冷数据










