ReferenceQueue、Reference详解
[toc]
ReferenceQueue
引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中,ReferenceQueue实现了入队(enqueue)和出队(poll),还有remove)操作,内部元素head就是泛型的Reference:
1 | private volatile Reference<? extends T> head = null; |
ReferenceQueue的实现是由Reference自身的链表结构(单向循环链表)所实现的。所以说ReferenceQueue名义上是一个队列,但实际内部并非有实际的存储结构,它的存储是依赖于内部节点之间的关系来表达。来看下它的入队方法:
1 | boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */ |
从enqueue()代码可以看出来,queue仅存储当前的head节点,而后面的节点由每个reference节点通过自己的next来保持的。而且queue是一个后进先出的队列。当新的节点进入时成为了head,然后出队时也是先出的head。
Reference
java.lang.ref.Reference是SoftReference、WeakReference、PhantomReference的基类。Reference对象是和垃圾回收密切配合实现,Reference的直接子类都是由JVM定制化处理的,因此在代码中直接继承于Reference类型没有任何作用。但可以继承JVM定制的Reference的子类。例如:Cleaner就继承了PhantomReference。
构造函数
Reference内部提供2个构造函数,一个带queue,一个不带queue。
1 | Reference(T referent) { |
其中queue的意义在于可以在外部对这个queue进行监控。如果有对象即将被回收,那么相应的reference对象就会被放到这个queue里,我们就拿到reference再作一些事情。而如果不带的话,就只有不断地轮询reference对象,通过判断里面的get是否返回null,来判断是否被回收。phantomReference对象不能这样作,其get始终返回null,因此它只有带queue的构造函数。queue的默认值是ReferenceQueue.NULL。
以上这两种构造方法均有相应的使用场景,取决于实际的应用。如WeakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收。而ThreadLocalMap则采用判断get()是否为null来作处理。
如果在创建一个引用对象时,指定了ReferenceQueue,那么当引用对象指向的对象达到合适的状态(根据引用类型不同而不同)时,GC会把引用对象本身添加到这个队列中,方便我们处理它,因为“引用对象指向的对象GC会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己清理。”
Reference内部主要的成员
1 | private T referent; /* Treated specially by GC */ |
referent表示其引用的对象,即在构造的时候需要被包装在其中的对象。
queue是对象即将被回收时所要通知的队列。当对象即将被回收时,整个reference对象,而不仅仅是被回收的对象,会被放到queue里面,然后外部程序即可通过监控这个queue拿到相应的数据了。
next即当前引用节点所存储的下一个即将被处理的节点。但next仅在放到queue中才会有意义,因为只有在enqueue的时候,会将next设置为下一个要处理的Reference对象。为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了。而是引用一个特殊的ENQUEUED(内部定义的一个空队列)。因为已经放到队列当中,并且不会再次放到队列当中。
discovered表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队,这边只需要不停地拿到pending,然后再通过discovered不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。它是被JVM使用的。
pending是等待被入队的引用列表。JVM收集器会添加引用到这个列表,直到Reference-handler线程移除了它们。这个列表使用discovered字段来连接它下一个元素(即pending的下一个元素就是discovered对象。r = pending; pending = r.discovered)。
Reference状态值
每个引用对象都有相应的状态描述,即描述自己以及其包装的对象当前处于一个什么样的状态,以方便进行查询,定位或处理。Reference有4种状态。四种状态用Reference的成员变量queue与next来标示。
Active:活动状态,新创建的引用对象都是这个状态,即相应的对象为强引用状态。在这个状态下next == null,queue为构造Reference对象时传入的ReferenceQueue对象,默认是ReferenceQueue.NULL。在GC检测到引用对象的可达性发生变化之后,它的状态将变化为Pending或Inactive(如果引用对象在构造时指定了ReferenceQueue,那么转移到Pending;如果没指定,转移到Inactive)。
Pending:准备放入queue当中的引用对象,在这个状态的对象将挨个地排队放到queue当中。在这个时间窗口期,相应的对象为Pending状态,都在pending列表中。此状态的下,next == this(由JVM设置),queue为定义时所引用的queue。这里需要注意的是,queue如果指定了,那么引用对象能转移到Pending,如果没指定,直接转移到Inactive,因为此时next == this,queue == ReferenceQueue.NULL。
Enqueued:引用对象已经放到queue当中了。准备由外部线程来询循queue获取相应的数据。调用ReferenceQueue.enqueued()后的reference就会处于这个状态中。此状态中next 为该queue中的下一个引用,如果是该队列中的最后一个,那么为this,queue = ReferenceQueue.ENQUEUED。当reference实例从它的ReferenceQueue移除后,它将成为Inactive。没有注册queue的实例不会进入这个状态。
Inactive:即此对象已经由外部从queue中获取到,并且已经处理掉了,此时next == this,queue == ReferenceQueue.NULL。即意味着此引用对象可以被回收,并且对内部封装的对象也可以被回收掉了。但实际的回收运行取决于clear()方法是否被调用(clear()方法会设置this.referent = null)。
Reference类加载后,就会启动Reference Handler线程:
1 | static { |
从源码可以看出,这个线程在Reference类的static构造块中启动,并且被设置为最高优先级MAX_PRIORITY和daemon状态,并随即启动该线程。
1 | private static class ReferenceHandler extends Thread { |
此线程要做的事情就是不断的检查pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。
当Reference内部的referent对象的可达状态改变时,JVM会将该Reference对象放入pending链表,注意这时JVM会设置referent对象的next = this。并且这里enqueue的队列是在初始化Reference对象时传进来的queue,如果传入了null(实际使用的是ReferenceQueue.NULL),则ReferenceHandler则不进行enqueue操作,这时referent对象的状态已经是Inactive了。只有非RefernceQueue.NULL的queue才会将Reference进行enqueue。
在enqueue()中,当reference实例的queue!=null && queue != ENQUEUED时;设置queue为ENQUEUED,next为下一个要处理的reference对象,或者若为最后一个则next==this。这时referent对象是Enqueue。
当调用queue的remove或者poll方法时,就会将要处理的reference实例的queue设置为ReferenceQueue.NULL,next = this,此时reference实例就进入了Inactive状态,等待JVM回收。
具体执行过程:
1 | // 创建一个引用队列 |
转载自:
http://www.jianshu.com/p/f86d3a43eec5
http://www.cnblogs.com/jabnih/p/6580665.html