能省内存,String.intern()将内容相同的字符串统一指向堆中常量池的唯一引用,适用于日志状态码、配置键等低基数重复场景;误用(如不接收返回值、对动态拼接字符串调用)反而增加内存。

String.intern() 真的能省内存吗?先看它干了什么
String.intern() 不是“压缩字符串”,而是把字符串对象的引用统一指向字符串常量池(JDK 7+ 后该池在堆中)里的唯一实例。只要内容相同,多次调用 intern() 都返回同一个对象引用。
但注意:它只对内容相等的字符串生效,且不改变原字符串对象的生命周期——你得主动用返回值替换掉原来的引用,否则白 intern。
常见错误现象:str.intern(); 却没接返回值,原变量仍指向堆上新对象,常量池里多存一份,内存反而多占。
低基数重复字符串场景下怎么安全替换
适用场景:日志中的状态码("SUCCESS", "FAILED")、配置项键名("timeout", "retries")、枚举式业务字段("SHIPPED", "PENDING")等,总量大(百万级)、取值少(几十种以内)。
实操建议:
- 读取原始字符串后立刻调用
intern()并赋值给变量:status = status.intern(); - 如果字符串来自 JSON 解析、CSV 行解析等批量入口,把
intern()塞进解析逻辑最内层,避免中间 String 对象逃逸 - 不要对动态拼接字符串(如
"user_" + id)盲目 intern——这类基本不会重复,还拖慢性能 - JDK 8u20 之后可配
-XX:StringTableSize=65536(默认 60013),若重复字符串种类超万,增大该值能减少哈希冲突,提升 intern 效率
为什么有时 intern 后内存反而涨了
这不是 bug,是典型误用导致的泄漏:
- 字符串本身由大数组(
char[]或byte[])支撑,而intern()后的常量池引用会阻止整个原字符串对象被回收——如果原字符串是从一个超长文本中substring()出来的(JDK 7u6 以前),它可能持有一个巨大的底层数组,intern 后这个大数组被常量池间接持有,无法释放 - 使用
new String("abc").intern()这类写法,等于先在堆上造了个冗余对象,再把它 intern,堆上那个对象短期还在,GC 压力反增 - G1 GC 下,字符串常量池在老年代,大量 intern 可能提前触发混合回收;ZGC/Shenandoah 虽无此压力,但 intern 本身是同步方法,高并发下调用会争抢全局字符串表锁
比 intern 更稳的替代方案有哪些
intern() 是双刃剑,真正生产环境更推荐:
- 用静态 final 字符串常量做映射表,比如
public static final String STATUS_SUCCESS = "SUCCESS";,解析时用map.get(input)查表返回统一引用 - 使用
Enum替代字符串状态值,JVM 天然单例,无 intern 开销,类型安全 - Guava 的
Interners.newStrongInterner(),基于ConcurrentMap实现,可控、可预热、不依赖 JVM 全局表,适合需要隔离或测试的场景 - 若必须用 intern,至少加一层保护:只对长度 ≤ 64、且正则匹配
^[a-zA-Z0-9_-]+$的字符串调用,过滤掉可能含敏感内容或超长随机值的输入
常量池不是垃圾场,intern 不是银弹。真正省内存的关键,是让重复字符串“从出生起就共享引用”,而不是等它们满堆飞了再挨个抓回来塞池子。











