引用隊列,在檢測到適當的可到達性更改後,垃圾回收器將已註冊的引用對象添加到該隊列中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/