早上醒来突然想到一个问题,WeakHashMap
中的对象是如何在gc的时候被自动回收的。
按理说这应该是个很简单的问题,WeakHashMap
的Entry是WeakReference
类型的,而WeakReference
类型在每次gc的时候都会被回收,之前看代码的时候看到这里就结束了,没再往下看。可是仔细想了下,WeakHashMap
的实现使用数组加链表,每一个元素肯定会有一个Entry数组或者Entry.next引用,也就是说WeakHashMap
中每个元素都是Strong Reference的,不可能会自动回收。赶紧又翻了下代码,果然找到了不一样的地方:在每次get或者put的时候都会调用一个getTable
的方法,而getTable
里又调用了expungeStaleEntries
,这里才是奥秘所在。
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;
}
}
}
}
其逻辑如下:
- 遍历Entry元素注册的
ReferenceQueue
,在Reference.ReferenceHandler
中可以看到GC回收掉的WeakReference元素会放入这个队列,等待进一步处理,每个元素标记为e - 获取上一步的元素e在Entry数组中的索引,然后遍历这个数组元素所在的链表,找到等于e的元素,value置为null(这里不能将整个entry置为null,可能还在
HashIterator
中使用,但是这里已经解除了和entry数组和链表的引用关系,如果没有HashIterator
引用到就会被gc了)。
expungeStaleEntries
执行完后所有被放入ReferenceQueue
中的对象都从map中删除,从而实现所谓的自动回收。
回顾下这个过程有几个地方之前是没理清的:
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object o1 = new Object();
WeakReference<Object> weak = new WeakReference<>(o1, queue);
o1 = null;
System.gc();
-
引用对象和被引用对象。在上面的例子里,weak是引用对象,o1是被引用对象。API中有句话把这个关系说的很清楚
A reference object encapsulates a reference to some other object so that the reference itself may be examined and manipulated like any other object.
引用对象封装了一个到别的对象的引用,因此引用对象本身可以像其他对象那样检查和操作。 -
可达性。同样是API中的描述:
- An object is strongly reachable if it can be reached by some thread without traversing any reference objects. A newly-created object is strongly reachable by the thread that created it.
- An object is softly reachable if it is not strongly reachable but can be reached by traversing a soft reference.
- An object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference. When the weak references to a weakly-reachable object are cleared, the object becomes eligible for finalization.
- An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.
Finally, an object is unreachable, and therefore eligible for reclamation, when it is not reachable in any of the above ways.
强可达性是指没有别的引用对象引用到,且从某个线程中可以访问到;弱可达性是指既不是强可达性,也不是软可达性,但是有Weak Reference引用到。因此上面的o1是弱可达性,weak是强可达。System.gc的时候回收的是o1,而不是weak;weak只会被放入queue中,System.gc后可以通过queue.poll获取到。
-
ReferenceQueue
的使用。上面分析expungeStaleEntries
函数的时候可以看到这个引用队列的作用,但是对于WeakReference
和SoftReference
更多的时候我们并不希望被引用对象已经被回收之后还要自己去处理引用对象本身,因此构造的时候经常是使用没有ReferenceQueue
的版本。PhantomReference
只有一个构造函数,必须提供ReferenceQueue
参数(虽然可以传入null,但是这样的PhantomReference
就没有实际意义了,使用PhantomReference
的目的就是为了从ReferenceQueue
中获取phantom引用做进一步处理的)。这个队列的入队操作在Reference.ReferenceHandler
中进行。
网友评论