在了解 Weak Reference 之前 , 先给出一段简单的代码 :
public class WeakReferenceTest { public static void main(String[] args) throws Exception { Object o = new Object(); // 默认的构造函数,会使用ReferenceQueue.NULL 作为queue WeakReference<Object> wr = new WeakReference<Object>(o); System.out.println(wr.get() == null); o = null; System.gc(); System.out.println(wr.get() == null); } }输出结果 :false,true
喜欢探求究竟的童鞋会问 , 为啥 System .gc 后 Weak Reference 马上会被回收 , 怎么做到的呢 ? 让我们一起来深入 Reference 的源码探求个究竟 . 内部有两点需要注意 :
1) pending和 discovered成员:
先看 :pending 对象
/* List of References waiting to be enqueued. The collector adds * References to this list, while the Reference-handler thread removes * them. This list is protected by the above lock object. */ private static Reference pending = null;这个对象,定义为private,并且全局没有任何给它赋值的地方,根据它上面的注释,我们了解到这个变量是和垃圾回收期打交道的
再看discovered,同样为private,上下文也没有任何地方使用它
transient private Reference<T> discovered; /* used by VM */
看到了它的注释也明确写着是给VM用的。
上面两个变量对应在VM中的调用,可以参考openjdk中的hotspot源码,在hotspot/src/share/vm/memory /referenceProcessor.cpp 的ReferenceProcessor::discover_reference 方法。(根据此方法的注释由了解到虚拟机在对Reference的处理有ReferenceBasedDiscovery和 RefeferentBasedDiscovery两种策略)
2) ReferenceHandler 线程
这个线程在Reference类的static构造块中启动,并且被设置为高优先级和daemon状态。
此线程要做的事情,是不断的检查pending 是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。
通过这2点,我们来看整个过程:
pending是由jvm来赋值的,当Reference内部的referent对象的可达状态改变时,jvm会将Reference对象放入pending链表。
结 合代码eg1中的 o = null; 这一句,它使得o对象满足垃圾回收的条件,并且在后边显式的调用了 System.gc(),垃圾收集进行的时候会标记WeakReference所referent的对象o为不可达(使得 wr.get()==null),并且通过 赋值给pending ,触发ReferenceHandler线程处理pending。
ReferenceHandler 线程要做的是将pending对象enqueue,但默认我们所提供的queue,也就是从构造函数传入的是null,实际是使用了 ReferenceQueue.NULL,Handler线程判断queue为ReferenceQueue.NULL则不进行操作,只有非 ReferenceQueue.NULL 的queue才会将Reference进行enqueue。
ReferenceQueue.NULL相当于我们提供了一个空的Queue去监听垃圾回收器给我们的反馈 ( 什么反馈呢 ? 是说这个 quene 是给我们来用的么 , 例如 WeakHashMap 中使用的那种方式 ) ,并且对这种反馈不做任何处理。 ( 但垃圾还是回收了 ??? 不是 poll 时候做的事情么 ?)
要处理反馈,则必须要提供一个非ReferenceQueue.NULL的queue。这个quene可以看做是GC与应用程序的一个桥梁,告知应用需要对那些reference进行处理.
当一个 WeakReference 开始返回 null 时, 它所指向的对象已经准备被回收, 这时可以做一些合适的清理工作 . 将一个 ReferenceQueue 传给一个 Reference 的构造函数, 当对象被回收时, 虚拟机会自动将这个对象插入到 ReferenceQueue 中, WeakHashMap 就是利用 ReferenceQueue 来清除 key 已经没有强引用的 entries.
在WeakHashMap则在内部提供了一个非NULL的ReferenceQueue
private final ReferenceQueue<K> queue = new ReferenceQueue<K>();
在 WeakHashMap 添加一个元素时,会使用 此queue来做监听器。
见put方法中的下面一句:
tab[i] = new Entry<K,V>(k, value, queue, h, e); 这里Entry是一个内部类,继承了WeakReference class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V>WeakHashMap的 put, size, clear 都会间接或直接的调用到 expungeStaleEntries()方法。
顾名思义,此方法的作用就是将 queue中陈旧的Reference进行删除,因为其内部的referent都已经不可达了。所以也将这个WeakReference包装的key从map中删除。
总结:ReferenceQueue是作为 JVM GC与上层Reference对象管理之间的一个消息传递方式,它使得我们可以对所监听的对象引用可达发生变化时做一些处理,WeakHashMap正是利用此来实现的。
用图来大致表示如下: