引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中html
实现了一个队列的入队(enqueue)和出队(poll还有remove)操做,内部元素就是泛型的Reference,而且Queue的实现,是由Reference自身的链表结构( 单向循环链表 )所实现的。java
ReferenceQueue名义上是一个队列,但实际内部并不是有实际的存储结构,它的存储是依赖于内部节点之间的关系来表达。能够理解为queue是一个相似于链表的结构,这里的节点其实就是reference自己。能够理解为queue为一个链表的容器,其本身仅存储当前的head节点,然后面的节点由每一个reference节点本身经过next来保持便可。并发
r.next = (head == null) ? r : head; head = r;
而后,在获取的时候,采起相应的逻辑:jvm
Reference<? extends T> r = head; if (r != null) { head = (r.next == r) ? null : r.next; // Unchecked due to the next field having a raw type in Reference r.queue = NULL; r.next = r;
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */ synchronized (lock) { // Check that since getting the lock this reference hasn't already been // enqueued (and even then removed) ReferenceQueue<?> queue = r.queue; if ((queue == NULL) || (queue == ENQUEUED)) { return false; } assert queue == this; r.queue = ENQUEUED; r.next = (head == null) ? r : head; head = r; queueLength++; if (r instanceof FinalReference) { sun.misc.VM.addFinalRefCount(1); } lock.notifyAll(); // ① return true; } }
① lock.notifyAll(); 👈通知外部程序以前阻塞在当前队列之上的状况。( 即以前一直没有拿到待处理的对象,如ReferenceQueue的remove()方法 )ide
java.lang.ref.Reference 为 软(soft)引用、弱(weak)引用、虚(phantom)引用的父类。函数
由于Reference对象和垃圾回收密切配合实现,该类可能不能被直接子类化。
能够理解为Reference的直接子类都是由jvm定制化处理的,所以在代码中直接继承于Reference类型没有任何做用。但能够继承jvm定制的Reference的子类。
例如:Cleaner 继承了 PhantomReference.
public class Cleaner extends PhantomReference<Object>
oop
其内部提供2个构造函数,一个带queue,一个不带queue。其中queue的意义在于,咱们能够在外部对这个queue进行监控。即若是有对象即将被回收,那么相应的reference对象就会被放到这个queue里。咱们拿到reference,就能够再做一些事务。this
而若是不带的话,就只有不断地轮询reference对象,经过判断里面的get是否返回null( phantomReference对象不能这样做,其get始终返回null,所以它只有带queue的构造函数 )。这两种方法均有相应的使用场景,取决于实际的应用。如weakHashMap中就选择去查询queue的数据,来断定是否有对象将被回收。而ThreadLocalMap,则采用判断get()是否为null来做处理。spa
/* -- Constructors -- */ Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
若是咱们在建立一个引用对象时,指定了ReferenceQueue,那么当引用对象指向的对象达到合适的状态(根据引用类型不一样而不一样)时,GC 会把引用对象自己添加到这个队列中,方便咱们处理它,由于**“引用对象指向的对象 GC 会自动清理,可是引用对象自己也是对象(是对象就占用必定资源),因此须要咱们本身清理。”**.net
① pending 和 discovered
/* 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. The * list uses the discovered field to link its elements. */ private static Reference<Object> pending = null; /* When active: next element in a discovered reference list maintained by GC (or this if last) * pending: next element in the pending list (or null if last) * otherwise: NULL */ transient private Reference<T> discovered; /* used by VM */
能够理解为jvm在gc时会将要处理的对象放到这个静态字段上面。同时,另外一个字段discovered:表示要处理的对象的下一个对象。便可以理解要处理的对象也是一个链表,经过discovered进行排队,这边只须要不停地拿到pending,而后再经过discovered不断地拿到下一个对象赋值给pending便可,直到取到了最有一个。由于这个pending对象,两个线程均可能访问,所以须要加锁处理。
if (pending != null) { r = pending; // 'instanceof' might throw OutOfMemoryError sometimes // so do this before un-linking 'r' from the 'pending' chain... c = r instanceof Cleaner ? (Cleaner) r : null; // unlink 'r' from 'pending' chain pending = r.discovered; r.discovered = null;
② referent
private T referent; /* Treated specially by GC */
👆referent字段由GC特别处理 referent:表示其引用的对象,即咱们在构造的时候须要被包装在其中的对象。对象即将被回收的定义:此对象除了被reference引用以外没有其它引用了( 并不是确实没有被引用,而是gcRoot可达性不可达,以免循环引用的问题 )。若是一旦被回收,则会直接置为null,而外部程序可经过引用对象自己( 而不是referent,这里是reference#get() )了解到回收行为的产生( PhntomReference除外 )。
③ next
/* When active: NULL * pending: this * Enqueued: next reference in queue (or this if last) * Inactive: this */ @SuppressWarnings("rawtypes") Reference next;
next:即描述当前引用节点所存储的下一个即将被处理的节点。但next仅在放到queue中才会有意义( 由于,只有在enqueue的时候,会将next设置为下一个要处理的Reference对象 )。为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了。而是引用一个特殊的ENQUEUED。由于已经放到队列当中,而且不会再次放到队列当中。
④ discovered
/* When active: next element in a discovered reference list maintained by GC (or this if last) * pending: next element in the pending list (or null if last) * otherwise: NULL */ transient private Reference<T> discovered; /* used by VM */
👆被VM使用
discovered:当处于active状态时:discoverd reference的下一个元素是由GC操纵的( 若是是最后一个了则为this );当处于pending状态:discovered为pending集合中的下一个元素( 若是是最后一个了则为null );其余状态:discovered为null
⑤ lock
static private class Lock { } private static Lock lock = new Lock();
lock:在垃圾收集中用于同步的对象。收集器必须获取该锁在每次收集周期开始时。所以这是相当重要的:任何持有该锁的代码应该尽快完成,不分配新对象,而且避免调用用户代码。
⑥ 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. The * list uses the discovered field to link its elements. */ private static Reference<Object> pending = null;
pending:等待被入队的引用列表。收集器会添加引用到这个列表,直到Reference-handler线程移除了它们。这个列表被上面的lock对象保护。这个列表使用discovered字段来链接它本身的元素( 即pending的下一个元素就是discovered对象 )。
⑦ queue
volatile ReferenceQueue<? super T> queue;
queue:是对象即将被回收时所要通知的队列。当对象即被回收时,整个reference对象( 而不是被回收的对象 )会被放到queue里面,而后外部程序便可经过监控这个queue拿到相应的数据了。
这里的queue( 即,ReferenceQueue对象 )名义上是一个队列,但实际内部并不是有实际的存储结构,它的存储是依赖于内部节点之间的关系来表达。能够理解为queue是一个相似于链表的结构,这里的节点其实就是reference自己。能够理解为queue为一个链表的容器,其本身仅存储当前的head节点,然后面的节点由每一个reference节点本身经过next来保持便可。
jvm并不须要定义状态值来判断相应引用的状态处于哪一个状态,只须要经过计算next和queue便可进行判断。
经过这个组合,收集器只须要检测next属性为了决定是否一个Reference实例须要特殊的处理:若是next==null,则实例是active;若是next!=null,为了确保并发收集器可以发现active的Reference对象,而不会影响可能将enqueue()方法应用于这些对象的应用程序线程,收集器应经过discovered字段连接发现的对象。discovered字段也用于连接pending列表中的引用对象。
👆外部从queue中获取Reference
Q:👆若是PhantomReference对象无论enqueue仍是没有,都不会清除掉reference对象,那么怎么办?这个reference对象不就一直存在这了??并且JVM是会直接经过字段操做清除相应引用的,那么是否是JVM已经释放了系统底层资源,但java代码中该引用还未置null??
A:不会的,虽然PhantomReference有时候不会调用clear,如Cleaner对象 。但Cleaner的clean()方法只调用了remove(this),这样当clean()执行完后,Cleaner就是一个无引用指向的对象了,也就是可被GC回收的对象。
active ——> pending :Reference#tryHandlePending
pending ——> enqueue :ReferenceQueue#enqueue
enqueue ——> inactive :Reference#clear
① clear()
/** * Clears this reference object. Invoking this method will not cause this * object to be enqueued. * * <p> This method is invoked only by Java code; when the garbage collector * clears references it does so directly, without invoking this method. */ public void clear() { this.referent = null; }
调用此方法不会致使此对象入队。此方法仅由Java代码调用;当垃圾收集器清除引用时,它直接执行,而不调用此方法。
clear的语义就是将referent置null。
清除引用对象所引用的原对象,这样经过get()方法就不能再访问到原对象了( PhantomReference除外 )。从相应的设计思路来讲,既然都进入到queue对象里面,就表示相应的对象须要被回收了,由于没有再访问原对象的必要。此方法不会由JVM调用,而JVM是直接经过字段操做清除相应的引用,其具体实现与当前方法相一致。
② ReferenceHandler线程
static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); Thread handler = new ReferenceHandler(tg, "Reference Handler"); /* If there were a special system-only priority greater than * MAX_PRIORITY, it would be used here */ handler.setPriority(Thread.MAX_PRIORITY); handler.setDaemon(true); handler.start(); // provide access in SharedSecrets SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { [@Override](https://my.oschina.net/u/1162528) public boolean tryHandlePendingReference() { return tryHandlePending(false); } }); }
其优先级最高,能够理解为须要不断地处理引用对象。
private static class ReferenceHandler extends Thread { private static void ensureClassInitialized(Class<?> clazz) { try { Class.forName(clazz.getName(), true, clazz.getClassLoader()); } catch (ClassNotFoundException e) { throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e); } } static { // pre-load and initialize InterruptedException and Cleaner classes // so that we don't get into trouble later in the run loop if there's // memory shortage while loading/initializing them lazily. ensureClassInitialized(InterruptedException.class); ensureClassInitialized(Cleaner.class); } ReferenceHandler(ThreadGroup g, String name) { super(g, name); } public void run() { while (true) { tryHandlePending(true); } } }
③ tryHandlePending()
/** * Try handle pending {[@link](https://my.oschina.net/u/393) Reference} if there is one.<p> * Return {[@code](https://my.oschina.net/codeo) true} as a hint that there might be another * {[@link](https://my.oschina.net/u/393) Reference} pending or {@code false} when there are no more pending * {@link Reference}s at the moment and the program can do some other * useful work instead of looping. * * @param waitForNotify if {@code true} and there was no pending * {@link Reference}, wait until notified from VM * or interrupted; if {@code false}, return immediately * when there is no pending {@link Reference}. * @return {@code true} if there was a {@link Reference} pending and it * was processed, or we waited for notification and either got it * or thread was interrupted before being notified; * {@code false} otherwise. */ static boolean tryHandlePending(boolean waitForNotify) { Reference<Object> r; Cleaner c; try { synchronized (lock) { if (pending != null) { r = pending; // 'instanceof' might throw OutOfMemoryError sometimes // so do this before un-linking 'r' from the 'pending' chain... c = r instanceof Cleaner ? (Cleaner) r : null; // unlink 'r' from 'pending' chain pending = r.discovered; r.discovered = null; } else { // The waiting on the lock may cause an OutOfMemoryError // because it may try to allocate exception objects. if (waitForNotify) { lock.wait(); } // retry if waited return waitForNotify; } } } catch (OutOfMemoryError x) { // Give other threads CPU time so they hopefully drop some live references // and GC reclaims some space. // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above // persistently throws OOME for some time... Thread.yield(); // retry return true; } catch (InterruptedException x) { // retry return true; } // Fast path for cleaners if (c != null) { c.clean(); return true; } ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; }
这个线程在Reference类的static构造块中启动,而且被设置为高优先级和daemon状态。此线程要作的事情,是不断的检查pending 是否为null,若是pending不为null,则将pending进行enqueue,不然线程进入wait状态。
因而可知,pending是由jvm来赋值的,当Reference内部的referent对象的可达状态改变时,jvm会将Reference对象放入pending链表。而且这里enqueue的队列是咱们在初始化( 构造函数 )Reference对象时传进来的queue,若是传入了null( 实际使用的是ReferenceQueue.NULL ),则ReferenceHandler则不进行enqueue操做,因此只有非RefernceQueue.NULL的queue才会将Reference进行enqueue。
ReferenceQueue是做为 JVM GC与上层Reference对象管理之间的一个消息传递方式,它使得咱们能够对所监听的对象引用可达发生变化时作一些处理
http://www.importnew.com/21633.html http://hongjiang.info/java-referencequeue/ http://www.cnblogs.com/jabnih/p/6580665.html http://www.importnew.com/20468.html http://liujiacai.net/blog/2015/09/27/java-weakhashmap/