String.intern()适用于低基数、高重复、长生命周期字符串(如日志状态、配置key、JSON字段名),可省内存、提==判断速度;但须赋值接收返回值,禁用于动态拼接、敏感信息和超长字符串,且需配合JVM参数调优。

直接用 intern() 能省内存、提速度,但前提是用对场景、接住返回值、避开陷阱。
适用场景:低基数、高重复、长生命周期的字符串
不是所有字符串都适合 intern。真正见效的是那些“种类少、出现多、用得久”的字符串,比如:
- 日志中的状态字段:
"SUCCESS"、"TIMEOUT"、"RETRY_EXHAUSTED" - 配置项 key:
"maxConnections"、"retryPolicy"、"enableTrace" - JSON 解析的字段名:
"id"、"name"、"createdAt"(尤其在微服务间高频 RPC 响应中) - 枚举式业务码:
"PENDING"、"SHIPPED"、"CANCELLED"
这类字符串通常总量达百万级,但取值仅几十种,intern 后能将原本分散在堆上的 N 个对象压缩为常量池中 1 个引用 + N 个轻量指针。
关键操作:必须赋值,不能白调
intern() 返回的是常量池里的引用,原变量仍指向旧对象——不赋值就等于没做。
错误写法(内存反而涨):
status = "FAILED";status.intern(); // 返回值丢弃,原 status 还是堆上新对象
正确写法(生效):
status = status.intern(); // 用返回值覆盖原引用// 或一步到位:
String status = new String("FAILED").intern();
批量处理时,建议在解析入口最内层统一拦截,例如:
- FastJSON:在
ObjectDeserializer中对每个 key 调用key.intern() - Jackson:自定义
KeyDeserializer,重写deserializeKey() - Gson:注册
TypeAdapter,读取nextName()后立刻 intern
避坑要点:三类典型误用
以下情况 intern 不但不省内存,还会拖慢系统甚至引发风险:
-
动态拼接字符串:如
"user_" + id、"trace-" + UUID.randomUUID()—— 几乎无重复,intern 白费哈希计算,还占常量池空间 -
含敏感信息的字符串:如
"password=xxx"、"token=abc123"—— intern 后长期驻留堆中,GC 不回收,增加泄露面 - 超长字符串:如 Base64 编码体、大段日志消息作为 key —— 常量池存的是引用,但底层数组仍被强引用,易触发老年代 GC
更稳妥的做法是预定义已知 key 集合,只对白名单字符串 intern,例如:
private static final Setif (KNOWN_KEYS.contains(key)) { key = key.intern(); }
性能增强:配合 JVM 参数调优
默认字符串常量池大小是 60013(JDK 8u20+),若高频 key 种类超 5000,哈希冲突升高,intern 效率下降。
可启动时指定更大容量:
-XX:StringTableSize=65536注意:该值需为 2 的幂次方,过大(如 >1M)会浪费哈希表空间,一般设为实际 key 种类数的 2–4 倍较合理。
另外,频繁 intern 在高并发下会争抢全局字符串表锁,若 QPS 极高,可考虑加一层本地缓存(如 ConcurrentHashMap)做两级去重,降低锁竞争。











