WeakHashMap能自动清理是因为其键使用弱引用,GC回收无强引用的键后,通过expungeStaleEntries()在后续操作中扫描并移除对应条目。

WeakHashMap 适合做临时缓存,核心在于它的键是弱引用(WeakReference),当键对象没有其他强引用指向它时,GC 可以回收该键,随后对应的键值对会在下一次哈希表清理时自动被移除。
为什么 WeakHashMap 能自动清理?
WeakHashMap 内部使用一个 弱引用的键数组,其 Entry 继承自 WeakReference<k></k>。这意味着:
- 键对象只被 WeakReference 持有,不阻止 GC;
- 当 GC 回收了某个键实例,对应 Entry 的 get() 返回 null;
- 下次调用 size()、get()、put() 等方法时,WeakHashMap 会扫描并清理这些“已失效”的 Entry(通过 expungeStaleEntries())。
⚠️ 注意:值对象仍是强引用!如果值也持有键的强引用(如循环引用),或值本身很大,需手动管理值的生命周期,否则可能造成内存泄漏。
构建安全的临时缓存类
直接用 WeakHashMap 做缓存容易忽略两点:值未及时释放、缺乏过期控制。推荐封装一层:
- 用
WeakHashMap<k cachevalue>></k>存储,其中CacheValue包含实际值 + 创建时间; - 每次
get()时检查是否过期(可选),并触发清理; - 避免在值中强引用键(例如不要在 value 里存 this 或外部 this 引用);
- 若需更精细控制(如 LRU、最大容量、定时淘汰),建议改用
java.util.concurrent.ConcurrentHashMap+ 引用队列 + 定时线程,或直接用 Caffeine。
简单示例:带超时的弱键缓存
// 示例:缓存用户配置,键是 User 实例,无强引用时自动失效
立即学习“Java免费学习笔记(深入)”;
public class WeakTimeoutCache<K, V> {
private final WeakHashMap<K, CacheEntry<V>> cache = new WeakHashMap<>();
private final long timeoutMs;
<pre class="brush:php;toolbar:false;">public WeakTimeoutCache(long timeoutMs) {
this.timeoutMs = timeoutMs;
}
public void put(K key, V value) {
cache.put(key, new CacheEntry<>(value));
}
public V get(K key) {
CacheEntry<V> entry = cache.get(key);
if (entry == null || entry.isExpired(timeoutMs)) {
cache.remove(key); // 主动清理过期项(非必须,但更及时)
return null;
}
return entry.value;
}
private static class CacheEntry<V> {
final V value;
final long createTime;
CacheEntry(V value) {
this.value = value;
this.createTime = System.currentTimeMillis();
}
boolean isExpired(long timeoutMs) {
return System.currentTimeMillis() - createTime > timeoutMs;
}
}}
使用时只需确保传入的 key(如 new User(123))不被其他地方长期强引用,一旦不可达,下次 GC 后再调用 get() 就返回 null。
常见陷阱与建议
-
键必须重写
equals()和hashCode():WeakHashMap 仍依赖哈希逻辑定位 Entry,若没正确实现,get()找不到值; - 不要用 String、Integer 等常量池对象作键:它们被 JVM 强引用,永远不会被回收,WeakHashMap 失去意义;
- 不适用于长期稳定缓存:弱引用不可控,GC 时间不确定,不适合要求“至少存活 X 分钟”的场景;
-
并发访问需额外同步:WeakHashMap 本身非线程安全,多线程下应包装为
Collections.synchronizedMap()或改用其他方案。


















