做安卓开发的人都知道,其用的是Java语言开发的,所以标题自然也是成立的。
以前设计缓存时也曾过用WeakHashMap来实现,对Java的Reference稍做过一些了解,其实这个问题,归根到底,是个Java GC的问题,由垃圾回收器与ReferenceQueue的交互方式决定的。WeakHashMap的实现也是通过ReferenceQueue这个“监听器”来优雅的实现自动删除那些引用不可达的key的。
先看看ReferenceQueue在Java中的描述:
Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected. 中文JavaDoc的描述:引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中
查看源代码会发现它很简单,实现了一个队列的入队(enqueue)和出队(poll还有remove)操作,内部元素就是泛型的Reference,并且Queue的实现,是由Reference自身的链表结构所实现的。
再来看 Reference类的代码,注意,javadoc中有一句,提到了它与GC是紧密相关的:
Because reference objects are implemented in close cooperation with the garbage collector, this class may not be subclassed directly.
从数据结构上看,Reference链表结构内部主要的成员有
private T referent; //就是它所指引的 Reference next; //指向下一个;
另一个比较重要的内部数据是:
ReferenceQueue queue;
这个queue是通过构造函数传入的,表示创建一个Reference时,要将其注册到那个queue上。
Queue的另一个作用是可以区分不同状态的Reference。Reference有4种状态,不同状态的reference其queue也不同:
- Active: queue = ReferenceQueue with which instance is registered, or ReferenceQueue.NULL if it was not registered with a queue; next = null. [/cpp]
- Pending: queue = ReferenceQueue with which instance is registered; next = Following instance in queue, or this if at end of list. [/cpp]
- Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instancein queue, or this if at end of list. [/cpp]
- Inactive: queue = ReferenceQueue.NULL; next = this. [/cpp]
那么,当我们创建了一个WeakReference,并且将其referent改变后,究竟发生了什么?先看一段代码:
// eg1 public static void test() throws Exception{ Object o = new Object(); // 默认的构造函数,会使用ReferenceQueue.NULL 作为queue WeakReferencewr = new WeakReference(o); System.out.println(wr.get() == null); o = null; System.gc(); System.out.println(wr.get() == null); }
结果大家都知道,但其内部是怎么实现的,还需重新看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,并且全局没有任何给它赋值的地方, //根据它上面的注释,我们了解到这个变量是和垃圾回收期打交道的。 [/cpp] 再看discovered,同样为private,上下文也没有任何地方使用它 transient private Reference
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去监听垃圾回收器给我们的反馈,并且对这种反馈不做任何处理。要处理反馈,则必须要提供一个非ReferenceQueue.NULL的queue。
在WeakHashMap则在内部提供了一个非NULL的ReferenceQueue private final ReferenceQueue
expungeStaleEntries顾名思义,此方法的作用就是将 queue中陈旧的Reference进行删除,因为其内部的referent都已经不可达了。所以也将这个WeakReference包装的key从map中删除。
个人认为:ReferenceQueue是作为 JVM GC与上层Reference对象管理之间的一个消息传递方式,它使得我们可以对所监听的对象引用可达发生变化时做一些处理,WeakHashMap正是利用此来实现的。用图来大致表示如下:
- 文章2300
- 用户1336
- 访客10863260
真正的成功是激励他人行动。