WeakHashMap 不能直接当缓存用,因其清理依赖GC和方法调用触发expungeStaleEntries(),不保证即时清除;键需不可变以保hashCode稳定,值为强引用需防内存滞留,不支持TTL/LRU,且非线程安全。
WeakHashMap 不能直接当缓存用,但可以作为自动清理的底层容器——关键在于你如何封装它,以及是否接受“不保证即时清除”这个前提。
WeakHashMap 的清理时机完全由 GC 和方法调用触发
它不会在键被回收后立刻从表中消失。只有当你调用
、
、
、
等方法时,内部的
才会顺手扫一遍引用队列,把已入队的失效条目真正移除。
如果你只往里
,之后再也不碰这个 map,哪怕 GC 已经回收了所有键,
仍可能返回非零值
调用
是建议而非命令,JVM 可能忽略;生产环境通常禁用该调用
清理动作是同步的,每次调用都可能带来微小延迟,尤其在大表 + 频繁 GC 后引用队列积压时
键对象必须保持 hashCode/equals 稳定
WeakHashMap 的哈希槽位依赖键的
计算。如果键对象在被 GC 回收前修改过内部状态(比如重写了
或
,且逻辑依赖可变字段),就可能导致:该键虽已入引用队列,但
在遍历 table 时无法正确定位其原始槽位,从而漏删。
典型翻车场景:用一个可变的
实例作键,又在它生命周期内调用了
导致
改变
安全做法:键类应设计为不可变(immutable),或至少确保
和
不随实例状态变化
注意:
可以作为键,但它的哈希行为是固定的,不会出问题
值对象不会自动断开强引用,需警惕内存滞留
WeakHashMap 只弱持有键,值仍是强引用。这意味着:一旦键被回收、条目被清理,值对象才失去一个强引用来源;但如果值本身还被其他地方持有着(比如某个静态集合、线程局部变量、未关闭的流),它就不会被 GC 回收。
常见误用:缓存一个大图片对象
,以该图片自身为键 —— 键被回收后,值(即图片)若没被其他代码释放,依然占内存
更稳妥的做法:让键是轻量对象(如请求 ID 字符串、UI 组件引用),值才是重资源;或配合软引用(
)包装值,增加一层回收弹性
不要指望 WeakHashMap 帮你解决值泄漏,它只管“键死了,我就删整条记录”
它不适合需要 TTL 或访问顺序淘汰的场景
WeakHashMap 没有时间戳、没有访问计数、不维护插入/访问顺序。它既不支持设置过期时间(TTL),也无法按最近最少使用(LRU)策略驱逐条目。
如果你需要“10 分钟后过期”,得自己加定时任务或包装一层带时间戳的 wrapper 类
如果你需要“保留最近 100 个查询结果”,WeakHashMap 无法保证数量上限——它只响应 GC,不响应访问频率
真要这些能力,优先考虑
或
;WeakHashMap 的价值只在“键生命周期天然短暂 + 你不想写清理逻辑”的交集里
真正容易被忽略的一点:WeakHashMap 不是线程安全的。并发读写必须外加同步,而加锁又可能阻塞
的执行,进一步拖慢清理节奏。如果缓存读写频繁,这点比“不实时”更致命。
getputsizekeySetexpungeStaleEntries()putsize()System.gc()hashCode()hashCode()equals()expungeStaleEntries()UserContextsetTenantId(...)hashCode()hashCode()equals()nullBufferedImageSoftReferenceCaffeineGuava CacheexpungeStaleEntries()