WeakHashMap用String字面量作key时条目永不回收,因常量池中字符串被JVM全局强引用,弱引用失效;应改用new String("x")或自定义对象作key。

WeakHashMap 用 String 字面量(如 "abc")作 key 时,条目不会被 GC 回收——这不是 bug,而是 JVM 常量池机制决定的。关键在于:常量池中的字符串由类加载器和运行时常量池强持有,WeakHashMap 的弱引用完全失效。
为什么 String 字面量作 key 永远不回收
Java 编译期生成的字符串字面量(例如 "key1"、"config")会被放入运行时常量池(属于方法区),该区域的对象默认被 JVM 全局强引用。即使 WeakHashMap 是唯一持有者,GC 也无法回收它,因为从 GC Roots 出发仍可达。
-
map.put("user", new User());→ "user" 在常量池,永不回收 -
map.put(new String("user"), new User());→ 新建对象在堆中,无强引用时下一次 GC 可能回收 -
String s = "user"; map.put(s, obj); s = null;→ 仍不回收,因常量池引用独立于局部变量
如何快速确认是否用了常量池 key
检查代码中 key 的创建方式:
- 直接双引号写法:
"abc"、"data_" + i(编译期可确定的字符串拼接也会进常量池) -
String.valueOf(...)、Integer.toString()等返回的通常是堆上新对象,但需结合具体实现验证 - 用
new String("x")或UUID.randomUUID().toString()等动态生成的字符串,才真正可被 GC
小技巧:打印 key 的 class 和 identity hash:System.out.println(key.getClass() + "@" + System.identityHashCode(key));。相同字面量的 key 总是同一个对象,而 new String() 每次都是不同实例。
立即学习“Java免费学习笔记(深入)”;
排查与修复建议
若发现 WeakHashMap size 不降、内存持续增长,且 key 多为字符串,优先怀疑常量池问题:
- 用 VisualVM 或 JFR 抓取 heap dump,筛选 WeakHashMap$Entry,查看 key 实例是否大量重复且哈希值一致
- 在测试中显式调用
System.gc()后检查map.size()是否变化;不变即大概率是常量池 key - 修复方式:改用堆上字符串,例如
new String("key"),或用String.intern()的反向逻辑(避免 intern) - 更稳妥方案:不用 String 作 key,改用自定义轻量对象(如
new KeyHolder("key")),确保它只被 WeakHashMap 弱引用
替代方案参考
如果业务确实需要字符串标识 + 自动清理,可考虑:
-
IdentityHashMap + 显式生命周期管理:用对象地址判等,配合业务逻辑在对象销毁时主动
remove(key) -
SoftReference 包装 value:当 key 是常量但 value 很大时,手动把 value 改为
SoftReference<Data>,缓解 OOM 风险 - Caffeine 或 Guava Cache:它们支持基于引用的驱逐策略(weakKeys()/weakValues()),内部已规避常量池陷阱,且线程安全


















