String.intern()可让重复字符串共享常量池引用,但需谨慎使用:JDK7+后常量池在堆中,虽避免PermGen溢出,但仍占堆内存且为全局同步操作;适用于日志级别、状态码等长生命周期重复字符串,不适用于UUID等唯一或短生命周期字符串;推荐用ConcurrentHashMap显式缓存替代。
string.intern() 可以让重复字符串共享同一份内存,但需谨慎使用——它不自动优化,也不适合所有场景。
intern() 的核心作用:把字符串对象“登记”进运行时常量池
调用 intern() 时,JVM 会检查字符串常量池中是否已存在内容相同的字符串:
- 如果存在,直接返回池中已有字符串的引用;
- 如果不存在,将当前字符串(或其副本)加入常量池,并返回该引用。
注意:JDK 7+ 后,常量池移到堆内存中,intern() 不再引发永久代(PermGen)溢出问题,但依然占用堆空间,且是全局同步操作,高并发下调用有性能开销。
适用场景:明确知道存在大量内容重复、生命周期长的字符串
典型例子包括:日志级别("INFO"、"ERROR")、状态码("SUCCESS"、"FAILED")、协议字段("GET"、"POST")、配置项键名等。这类字符串通常由用户输入、文件读取或网络接收而来,原始创建方式多为 new String(...) 或 substring()(JDK 7u6 之前易产生冗余),导致堆中堆积大量重复内容。
示例优化写法:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
String status = readFromJson().get("status"); // 可能是 new String("ERROR")
status = status.intern(); // 统一指向常量池中的 "ERROR"
后续所有相同内容的字符串都可复用该引用,减少对象数量和 GC 压力。
关键注意事项:不是“用了就省”,反而可能更费
- 不要对随机、唯一或短生命周期字符串 intern:比如 UUID、时间戳、临时拼接字符串,它们几乎不会重复,intern 后不仅不节省内存,还污染常量池,且无法被回收(直到 Full GC 或 JVM 退出);
- 避免在循环中无条件调用 intern():即使内容重复,每次调用仍需哈希查找+锁竞争,性能下降明显;
-
注意字符串来源:字面量(如
"hello")和通过String.valueOf()、Integer.toString()等生成的字符串,本身已走常量池路径,无需再 intern; -
验证效果要靠工具:用 JFR、VisualVM 或 MAT 对比 intern 前后的堆直方图(重点关注
java.lang.String实例数与 retained heap),而非凭感觉。
更稳健的替代方案:结合业务逻辑做显式去重
若字符串集有限且可预知,推荐用静态 Map<String, String> 或枚举管理:
private static final Map<String, String> STATUS_POOL = new ConcurrentHashMap<>();
public static String internStatus(String s) {
return STATUS_POOL.computeIfAbsent(s, k -> k);
}
这种方式可控性强、无全局锁、可按需清理(如用 WeakHashMap),也便于单元测试和监控。对不确定是否重复的批量数据,可先采样统计高频字符串,再决定是否启用 intern 或定制缓存。
真正有效的内存优化,靠的是理解数据特征 + 小步验证 + 工具佐证,而不是给每个字符串加一句 intern()。

















