WeakHashMap 原理

WeakHashMap 对 key 持有一个 WeakReference,对 value 持有一个 StrongReference,key 的 WeakReference 会和一个 ReferenceQueue 相关联,当没有 StrongReference 指向对应的 key 时,key 会在系统 GC 的时候被回收,同时对应的 Entry 会记录在 ReferenceQueue 中,稍后程序会在适当的时机(即 WeakHashMap 发生读写操作的时候)遍历整个 ReferenceQueue 来去除 value 的 StrongReference,以便系统回收 value 的内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//Android Api 25 WeakHashMap.java
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
//Entry 继承自 WeakReference,在构造函数中对 key 持有一个 WeakReference,同时使用 queue 监控引用
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
int hash;
Entry<K,V> next;

Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
...
}

//这个方法遍历 ReferenceQueue 并清楚 value 的引用,代码中有多个地方调用
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
...
}
}
}

所以在使用 WeakHashMap 的过程中需要注意,value 的引用是 StrongReference,只有当 key 没有 StrongReference,系统才回自动回收 value 的内存。

常见使用场景

  • 当需要为某个对象新增一些新的属性,但是又不想通过继承实现。这个时候可以使用 WeakHashMap,key 为该对象,value 为新增的属性,这样做的好处是当对象没有强引用的时候,对应的属性会被自动回收,避免了内存泄露。 实例:Android Api 25 SQLiteConnectionPool.java 中的 mAcquiredConnections
  • 不推荐 使用 WeakHashMap 来管理 listeners,平时在管理 listener 的时候,通常在 registerXXXListener() 之后,需要在合适的实际调用 unRegisterXXXListener(),以防内存泄露,如果通过 Collections.newSetFromMap(weakHashMap) 新建一个 WeakHashSet,再来管理 listener 的话,可以免去手动 unRegister。但是需要注意的是,如果 registerXXXListener() 传入的是匿名函数的话,由于没有强引用,listener 会很容易被回收,所以这种方式不推荐使用。 实例:Android Api 25 SharedPreferencesImpl.java 中的 mListeners
  • 不推荐 网上有种观点是可以利用 WeakHashMap 的自动回收来做缓存,其实这是不可取的。因为:
    • 由于 WeakHashMap 依赖 WeakReference 实现,而 WeakReference 在 Minor GC 的时候就可以回收了,所以如果用 WeakHashMap 来做缓存的话,会发现缓存经常失效。
    • 当 Key 不存在 StrongReference 的时候,value 就可能会被回收,这和实际应用场景不符,即使不存在引用,缓存也不应当失效,缓存失效应当由一定的逻辑在判断,而如果使用 WeakHashMap 的话,缓存的失效是有引用决定的,这样不利用提高缓存命中率。

Ref1