Reference和ReferenceQueue
我們都知道在堆里面存放著Java中幾乎所有的對象實例,垃圾收集器在對堆進行回收前,第一件事情就是要確定這些對象之中哪些還“存活”著,哪些已經“死去”。那么gc怎么判斷一個對象是不是垃圾呢
判斷對象是否存活有兩種計數算法:引用計數法、可達性分析法
引用計數法:在對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加一;當引用失效時,計數器值就減一 就是如果一個對象沒有被任何引用指向,則可視之為垃圾。
可達性分析法:通過一系列的稱為GC Roots的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈Reference Chain,當一個對象到GC Root沒有任何引用鏈相連時,則證明此對象是不可用的
這兩種方式我們不做過多的介紹。但我們可以發(fā)現無論是通過引用計數算法判斷對象的引用數量,還是通過可達性分析算法判斷對象是否引用鏈可達,判定對象是否存活都和引用離不開關系。
下來我們就開始說說引用
一、引用
要了解Reference和ReferenceQueue,我們需要先知道什么是引用。
我們用圖來展示一下 Java中new一個對象 在內存中的創(chuàng)建過程
我們可以看出創(chuàng)建一個對象并用一個引用指向它的過程:
在堆內存中創(chuàng)建一個Student類的對象(new Student())
在棧內存中聲明一個指向Student類型對象的變量(Student obj)
將棧內存中的引用變量obj指向堆內存中的對象new Student()。
這樣把一個對象賦給一個變量,這個變量obj就是引用
在JDK1.2版之前,Java里面的引用是很傳統(tǒng)的定義:
如果reference類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱該reference數據是代表某塊內存、某個對象的引用。
在JDK 1.2版之后,Java對引用的概念進行了擴充,將引用分為4種:
- 強引用(Strong Reference)
- 軟引用(Soft Reference)
- 弱引用(Weak Reference)
- 虛引用(Phantom Reference)
Java 中引入四種引用的目的就是讓程序自己決定對象的生命周期。JVM通過垃圾回收器對這四種引用做不同的處理,來實現對象生命周期的改變。關于這4種引用的介紹這里不詳細展開,本文主要介紹Reference和ReferenceQueue
二、ReferenceQueue
對于軟引用、弱引用和虛引用,都可以和一個引用隊列ReferenceQueue 聯(lián)合使用,如果軟/弱/虛引用中的對象被回收,那么軟/弱/虛引用就會被 JVM加入關聯(lián)的引用隊列ReferenceQueue中。 也就是說我們可以通過監(jiān)控引用隊列來判斷Reference引用的對象是否被回收,從而執(zhí)行相應的方法。
例如下面的例子. 如果弱引用中的對象(obj)被回收,那么軟引用weakRef就會被 JVM 加入到引用隊列queue 中。 這樣當我們想檢測obj對象是否被回收了 ,就可以從 queue中讀取相應的 Reference 來判斷obj是否被回收,從而執(zhí)行相應的方法。
三、Reference源碼(JDK8)
要理解Reference 源碼要從幾個方面來看:
我們打開Reference的源碼,可以看到最開始有一段注釋說明,介紹了引用的四種狀態(tài)Active ,Pending ,Enqueued ,Inactive
翻譯過來大意如下:
新創(chuàng)建的Reference實例的狀態(tài)是Active。GC檢測到Reference引用的實際對象的可達性發(fā)生某些改變后,它的狀態(tài)將變化為Pending或Inactive。Reference注冊了ReferenceQueue,則會切換為Pending,并且Reference會加入pending-Reference list中。
Reference沒有注冊ReferenceQueue,會切換為Inactive。
在pending-Reference list中等著被Reference-handler 入隊列queue中的元素就處于這個狀態(tài)。沒有注冊ReferenceQueue的實例是永遠不可能到達這一狀態(tài)。
Enqueued:在ReferenceQueue隊列中時Reference的狀態(tài),如果Reference從隊列中移除,會進入Inactive狀態(tài)
一旦一個實例變?yōu)镮nactive,則這個狀態(tài)永遠都不會再被改變。
它們的關系下圖很清晰的表示了出來
Reference源碼中并不存在一個成員變量用于描述Reference的狀態(tài),它是通過他的成員變量的存在性"拼湊出"對應的狀態(tài)。
然后我們看下Reference內部的成員變量
public abstract class Reference<T> {private T referent; volatile ReferenceQueue<? super T> queue;Reference next;transient private Reference<T> discovered;static private class Lock { }private static Lock lock = new Lock();private static Reference<Object> pending = null; }-
referent:指reference引用的對象
-
queue:引用隊列,Reference引用的對象被回收時,Reference實例會被放入引用隊列,我們可以從ReferenceQueue得到Reference實例,執(zhí)行我們自己的操作
-
next:下一個Reference實例的引用,Reference實例通過此構造單向的鏈表。ReferenceQueue并不是一個鏈表數據結構,它只持有這個鏈表的表頭對象header,這個鏈表就是由next構建起來的,next也就是鏈表當前節(jié)點的下一個節(jié)點
-
pending:等待加入隊列的引用列表,GC檢測到某個引用實例指向的實際對象不可達后,會將該pending指向該引用實例。pending與discovered一起構成了一個pending單向鏈表,pending為鏈表的頭節(jié)點,discovered為鏈表當前Reference節(jié)點指向下一個節(jié)點的引用,這個隊列是由jvm的垃圾回收器構建的,當對象除了被reference引用之外沒有其它強引用了,jvm的垃圾回收器就會將指向需要回收的對象的Reference都放入到這個隊列里面。這個隊列會由ReferenceHander線程來處理,它的任務就是將pending隊列中要被回收的Reference對象移除出來,
-
discovered:pending list中下一個需要被處理的實例,在處理完當前pending之后,將discovered指向的實例賦予給pending即可。所以這個pending就相當于是一個鏈表。
我們來看一個弱引用的回收過程,來了解他的成員變量和四種狀態(tài)的轉換
ReferenceQueue<Object> queue = new ReferenceQueue<>();WeakReference mWreference = new WeakReference(new Object(), queue); System.gc();Reference mReference = queue.remove();= pending-Reference列表中的下一個元素
四、ReferenceHandler
從上面的分析我們知道ReferenceHandle線程的主要功能就是把pending list中的引用實例添加到引用隊列ReferenceQueue中,并將pending指向下一個引用實例。
ReferenceHandler是Reference類的一個內部類,由Reference靜態(tài)代碼塊中建立并且運行的線程,只要Reference這個父類被初始化,該線程就會創(chuàng)建和運行,由于它是守護線程,除非JVM進程終結,否則它會一直在后臺運行 。
private static class ReferenceHandler extends Thread {...public void run() {while (true) {tryHandlePending(true);}}...}static boolean tryHandlePending(boolean waitForNotify) {...synchronized (lock) {//如果pending隊列不為空,則將第一個Reference對象取出if (pending != null) {//緩存pending隊列頭節(jié)點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//將頭節(jié)點指向discovered,discovered為pending隊列中當前節(jié)點的下一個節(jié)點,這樣就把第一個頭結點出隊了pending = r.discovered;//將當前節(jié)點的discovered設置為null;當前節(jié)點出隊,不需要組成鏈表了;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 waitedreturn waitForNotify;}}//將對象放入到它自己的ReferenceQueue隊列里ReferenceQueue<? super Object> q = r.queue;if (q != ReferenceQueue.NULL) q.enqueue(r);return true;}五、ReferenceQueue
-
ReferenceQueue:在Reference引用的對象被回收時,Reference對象進入到pending隊列, 由ReferenceHander線程處理后,Reference就被放到ReferenceQueue里面,然后我們就可以從ReferenceQueue里拿到reference,執(zhí)行我們自己的操作。這樣我們只需要 ReferenceQueue就可以知道Reference持有的對象是否被回收。
-
如果不帶ReferenceQueue的話,要想知道Reference持有的對象是否被回收,就只有不斷地輪訓reference對象,通過判斷里面的get是否為null(phantomReference對象不能這樣做,其get始終返回null,因此它只有帶queue的構造函數)。
-
這兩種方法均有相應的使用場景。如weakHashMap中就選擇去查詢queue的數據,來判定是否有對象將被回收.而ThreadLocalMap,則采用判斷get()是否為null來作處理;
-
ReferenceQueue只存儲了Reference鏈表的頭節(jié)點,真正的Reference鏈表的所有節(jié)點是存儲在Reference實例本身,Reference通過成員屬性next構建單向鏈表,ReferenceQueue提供了對Reference鏈表的入隊、poll、remove等操作
文章轉自
總結
以上是生活随笔為你收集整理的Reference和ReferenceQueue的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021快手奢侈品行业数据价值报告
- 下一篇: 常用日志门面和日志实现