基本原理
WeakHashMap特点是,当除了自身有对key的引用外,此key没有其他引用,那么WeakHashMap会在下次对WeakHashMap进行增删改查操作时及时丢弃该键值对,节约内存使用,此特性使得WeakHashMap非常适合构建缓存系统。
WeakHashMap是主要通过expungeStaleEntries函数的来实现移除其内部不用的entry从而达到的自动释放内存的目的。基本上只要对WeakHashMap的内容进行访问就会调用expungeStaleEntries函数,从而达到清除不再被外部引用的key对应的entry键值对。如果预先生成了WeakHashMap,而在GC以前又不曾访问该WeakHashMap,那么因为没有机会调用expungeStaleEntries函数,因此并不会回收不再被外部引用的key对应的entry。
Entry键值对
WeakHashMap的键值对Entry继承自WeakReference,并实现了Map.Entry
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
当弱引用指向的对象只能通过弱引用(没有强引用或弱引用)访问时,GC会清理掉该对象,之后,引用对象会被放到ReferenceQueue中。在Entry的构造函数中可以得知,通过super(key, queue)将key保存为弱引用,通过this.value = value将value保存为强引用。当key中的引用被gc掉之后,在下次访问WeakHashMap(调用expungeStaleEntries函数)时相应的entry也会自动被移除。
/**
* Creates new entry.
*/
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;
}
expungeStaleEntries():清除过期的条目
从ReferenceQueue中取出过期的entry,从WeakHashMap找到对应的entry,逐一删除
/**
* Expunges stale entries from the table.
*/
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
首先通过循环一直从queue中取过期entry直到取完为止
for (Object x; (x = queue.poll()) != null; )
然后通过加锁queue进行删除过期entry的操作
synchronized (queue) {
...
}
在同步代码块中先把从queue中取出的Object类型的数据强制转化为Entry对象e,然后计算此entry在桶的位置(table数组的下标i),然后开始遍历entry链表,如果此entry是链表头,设置此entry的后继为新的链表头
if (prev == e)
table[i] = next;
否则将此entry的前序节点的后继指针指向此entry的后继节点
else
prev.next = next;
最后设置被删除的entry的value为null,加速垃圾回收,接着修改size
e.value = null; // Help GC
size--;
访问WeakHashMap时,对过期条目进行清除
可以看到对WeakHashMap的增删改查操作都会直接或者间接的调用expungeStaleEntries()方法,达到及时清除过期entry的目的。
此特性会到导致两次调用size、get等方法可能会返回不一致的数据。