weakhashmap 的自动清理由弱引用、引用队列与方法调用协同完成,仅在 get、put、size 等操作前调用 expungestaleentries() 清理失效条目,不依赖定时扫描或后台线程,轻量且符合对象生命周期语义。

WeakHashMap 的缓存自动清理不是靠定时扫描或后台线程,而是由弱引用、引用队列和方法调用三者协同触发的被动机制。它不保证“立刻清除”,但能确保每次你操作它时,顺手把已失效的条目清掉——轻量、低开销,也符合对象生命周期语义。
键为什么能被自动回收
WeakHashMap 把每个键包装成 WeakReference,并注册到一个内部 ReferenceQueue。只要外部没有强引用再指向这个键对象,JVM 在下一次 GC 时就可能把它回收。回收后,该 Entry 会被放入引用队列,等待后续清理。
- 键是弱引用,不阻止 GC;值仍是强引用,只在键存活时有效
- Entry 类本身继承自 WeakReference,构造时就把键设为 referent,并绑定 queue
- String 字面量、Integer 小值等常量池对象通常有强引用驻留,不适合作为 WeakHashMap 的键
清理动作在哪发生
清理逻辑封装在私有方法 expungeStaleEntries() 中,但它不会主动运行,只在你调用常见方法前被触发:
- put():插入前先清理,避免哈希表膨胀
- get():查找前清理,防止命中已失效条目
- size()、isEmpty()、keySet()、entrySet():这些只读操作也会触发清理,否则 size 可能虚高
没有调用,就不会清理——这是它“懒”但高效的关键。
为什么适合内存敏感缓存
缓存的核心矛盾是既要提速,又怕占满堆内存。WeakHashMap 把决策权交给 JVM GC:
- 堆内存紧张时,GC 更频繁,弱引用键更容易被回收,缓存自然收缩
- 临时对象(如 UI 组件、请求上下文)一旦失去外部强引用,作为键会立即“退场”,不会拖慢 GC 或引发泄漏
- 无需手动维护 TTL、LRU 或清理线程,代码更简洁,语义更清晰
典型场景包括图片缩略图缓存(以原始 Image 对象为键)、监听器注册表(以 Listener 实例为键)、按请求上下文缓存中间结果等。
实际使用中必须注意的限制
自动清理虽方便,但不是万能解药:
- GC 不可控:System.gc() 是建议而非命令,生产环境通常禁用;清理时机取决于 JVM 实际行为,无法保证实时性
- 键必须可重入哈希:若键对象在被回收前修改了 hashCode 或 equals,可能导致 Entry 卡在 table 里找不到,无法清理
- value 是强引用:如果 value 反向持有 key(比如通过内部类、lambda、Handler、setTag 等),会形成引用链,导致 key 无法被回收,缓存“失弱”
- size() 和 containsKey() 不可靠:它们返回的是当前未被 GC 的 entry 数,不是“已插入数”;刚 put 后 get 可能返回 null
不复杂但容易忽略。










