weakhashmap 不阻止 key 被 gc 因其 key 用 weakreference 包装,无强引用时 gc 可回收;但 value 仍被强引用,若 value 反向引用 key 会导致内存泄漏,且清理惰性、非线程安全。

WeakHashMap 为什么不会阻止 key 被 GC 回收
因为 WeakHashMap 内部用的是 WeakReference 包装 key,只要没有强引用指向该 key,下一次 GC 就可能把它清掉——value 是否还被引用不影响这个过程。这点和 HashMap 完全不同,后者会让 key 和 value 都一直留在堆里,直到 map 自身被回收。
常见错误现象:WeakHashMap 突然“变空”或查不到刚 put 进去的值,其实不是 bug,而是 key 对象(比如临时创建的 String、局部 new Object())很快被 GC 了。
- 使用场景:适合缓存“附属信息”,比如为某个对象动态附加元数据,且不希望因此延长其生命周期
- key 必须是可被 GC 的普通对象;不能用常量字符串(如
"abc")做 key,因为字符串常量池里的对象永远不会被回收 - value 仍会被强引用住,如果 value 又反向引用了 key 或其他大对象,照样引发内存泄漏
WeakHashMap 做缓存时必须配合引用队列清理 stale entry
WeakHashMap 不会立刻删除已失效的 entry,而是在每次调用 get、put、size 等方法时,顺手清理一部分。但这个清理是懒惰且不彻底的——如果长时间只读不写,失效 entry 会堆积,导致 size() 返回值虚高、内存实际没释放。
真实问题:缓存长期运行后发现 WeakHashMap.size() 越来越大,但实际有效 entry 很少。
立即学习“Java免费学习笔记(深入)”;
- 必须手动触发清理:在关键路径后调用
expungeStaleEntries()(它是 package-private,得用反射或继承绕过) - 更稳妥的做法是别直接用
WeakHashMap,改用java.lang.ref.ReferenceQueue+WeakReference自建结构,或选用Guava Cache/Caffeine,它们内置了及时清理逻辑 - 注意
expungeStaleEntries()是同步方法,高频调用会影响性能
WeakHashMap 无法解决 value 引用 key 导致的循环引用泄漏
很多人以为用了 WeakHashMap 就万事大吉,结果发现对象还是不被回收。典型原因是 value 持有对 key 的强引用(比如 value 是个内部类实例、或显式保存了 key 的引用),形成“key → value → key”闭环。
错误示例:
map.put(obj, new Wrapper(obj)); // Wrapper 内部持有 obj 引用
- GC 判断的是“从 GC Roots 是否可达”,只要 value 还活着,它持有的 key 就算被
WeakHashMap弱引用也照旧活得好好的 - 检查泄漏时,用 MAT 或 VisualVM 看对象的支配树(dominator tree),重点找 value 到 key 的强引用链
- 解决方案不是换 map,而是让 value 改用
WeakReference<k></k>存 key,或者干脆避免这种双向绑定设计
WeakHashMap 在多线程环境下不安全,但并发清理逻辑更危险
WeakHashMap 本身不是线程安全的,这点和 HashMap 一样。但它额外有个坑:内部的 ReferenceQueue 清理逻辑在多线程并发调用 get/put 时,可能触发 Entry 数组的并发修改,导致 ConcurrentModificationException 或静默数据丢失。
- 不要给
WeakHashMap加synchronized块就认为安全了——清理动作本身不在锁保护范围内 - 高并发场景下,优先考虑
ConcurrentHashMap+ 定期扫描 key 弱引用状态,或直接上Caffeine.newBuilder().weakKeys().build() - 哪怕单线程,如果 key 是跨线程共享的对象(比如被多个线程同时作为 key 使用),也要小心 GC 时机不可控带来的行为漂移
WeakHashMap 的边界很窄:它只解决“key 生命周期不该由 map 维持”这一个问题。一旦涉及 value 生命周期管理、并发访问、或需要精确控制回收时机,就得跳出这个类本身去设计——否则省下的几行代码,最后花几天 debug 都补不回来。









