WeakHashMap 能缓解缓存内存泄漏,因其键为弱引用,GC 可回收无强引用的键并使条目失效;但清理非即时,value 仍强引用可能导致循环引用,且不适用于常量池对象或需强一致性场景。

WeakHashMap 能缓解缓存引起的内存泄漏,核心在于它不阻止键对象被垃圾回收——只要外部不再强引用该键,GC 就能回收它,随后对应条目自动失效。但它不是“万能缓存”,用错场景或写法反而掩盖问题。
为什么普通 HashMap 容易导致缓存泄漏
HashMap 对键和值都保持强引用。即使业务逻辑早已丢弃某个 key(比如一个临时创建的 User 对象),只要它还留在 map 里,JVM 就不会回收它。长期运行后,这类“僵尸键”越积越多,占用堆内存,最终触发 OOM。
- 典型表现:监控显示老年代持续增长,Full GC 频繁但回收量小
- 常见诱因:用局部 new 出的对象作 key,却没在使用后从 map 中显式 remove
- 误区:以为“缓存只读不写就安全”,其实只读也锁住 key 生命周期
WeakHashMap 的真实行为机制
它把 key 包装成 WeakReference 存入内部 Entry。GC 发现该 key 没有其他强引用时,就会回收它,并将对应 Entry 标记为“过期”。但清理不是即时的——要等到下一次 get、put、size 等操作触发 expungeStaleEntries() 才真正移除。
- key 被回收后,get(key) 返回 null,不代表没存过,只说明 key 已不可达
- value 仍是强引用,若 value 又持有对 key 的引用(如内部类、闭包、显式字段),会形成循环引用,导致 key 实际无法回收
- String 字面量(如 "abc")、Integer.valueOf(123) 等常量池对象永不回收,不能当 key
安全使用 WeakHashMap 缓存的关键实践
它适合缓存“附属信息”,而非通用数据。重点不在“存得久”,而在“随 key 自动消失”。
立即学习“Java免费学习笔记(深入)”;
- 键必须是可被 GC 的普通对象,例如 new User()、new StringBuilder(),避免静态/常量/单例实例
- 值对象不要反向强引用 key;若需关联,改用 WeakReference 包装 value
- 多线程环境下必须加同步,WeakHashMap 本身非线程安全
- 避免依赖 size() 判断缓存状态,它可能虚高;遍历时需检查 entry.getKey() != null
更稳妥的替代方案
WeakHashMap 语义窄、清理惰性、易踩坑。现代项目中,优先考虑成熟缓存库:
- Caffeine 或 Guava Cache:支持弱键、弱值、软引用、LRU、TTL、自动清理,API 清晰且线程安全
- 自定义结构:结合 ReferenceQueue + WeakReference + ConcurrentHashMap,可控性更强
- 监听器/元数据绑定等生命周期依附型场景,WeakHashMap 仍有不可替代价值


















