詳解java中Reference的實現(xiàn)與相應(yīng)的執(zhí)行過程
一、Reference類型(除強引用)
可以理解為Reference的直接子類都是由jvm定制化處理的,因此在代碼中直接繼承于Reference類型沒有任何作用.只能繼承于它的子類,相應(yīng)的子類類型包括以下幾種.(忽略沒有在java中使用的,如jnireference)
SoftReference
WeakReference
FinalReference
PhantomReference
上面的引用類型在相應(yīng)的javadoc中也有提及.FinalReference專門為finalize方法設(shè)計,另外幾個也有特定的應(yīng)用場景.其中softReference用在內(nèi)存相關(guān)的緩存當中,weakReference用在與回收相關(guān)的大多數(shù)場景.phantomReference用在與包裝對象回收回調(diào)場景當中(比如資源泄漏檢測).
可以直接在ide中查看幾個類型的子類信息,即可了解在大多數(shù)框架中,都是通過繼承相應(yīng)的類型用在什么場景當中,以便于我們實際進行選型處理.
二、Reference構(gòu)造函數(shù)
其內(nèi)部提供2個構(gòu)造函數(shù),一個帶queue,一個不帶queue.其中queue的意義在于,我們可以在外部對這個queue進行監(jiān)控.即如果有對象即將被回收,那么相應(yīng)的reference對象就會被放到這個queue里.我們拿到reference,就可以再作一些事務(wù).
而如果不帶的話,就只有不斷地輪訓reference對象,通過判斷里面的get是否返回null(phantomReference對象不能這樣作,其get始終返回null,因此它只有帶queue的構(gòu)造函數(shù)).這兩種方法均有相應(yīng)的使用場景,取決于實際的應(yīng)用.如weakHashMap中就選擇去查詢queue的數(shù)據(jù),來判定是否有對象將被回收.而ThreadLocalMap,則采用判斷get()是否為null來作處理.
相應(yīng)的構(gòu)造函數(shù)如下所示:
Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
這里面的NULL隊列,即可以理解為不需要對其隊列中的數(shù)據(jù)作任何處理的隊列.并且其內(nèi)部也不會存取任何數(shù)據(jù).
在上面的對象中,referent表示其引用的對象,即我們在構(gòu)造的時候,需要被包裝在其中的對象.對象即將被回收的定義即此對象除了被reference引用之外沒有其它引用了(并非確實沒有被引用,而是gcRoot可達性不可達,以避免循環(huán)引用的問題).
queue即是對象即被回收時所要通知的隊列,當對象即被回收時,整個reference對象(而不是被回收的對象)會被放到queue里面,然后外部程序即可通過監(jiān)控這個queue拿到相應(yīng)的數(shù)據(jù)了.
三、ReferenceQueue及Reference引用鏈
這里的queue名義上是一個隊列,但實際內(nèi)部并非有實際的存儲結(jié)構(gòu),它的存儲是依賴于內(nèi)部節(jié)點之間的關(guān)系來表達.可以理解為queue是一個類似于鏈表的結(jié)構(gòu),這里的節(jié)點其實就是reference本身.可以理解為queue為一個鏈表的容器,其自己僅存儲當前的head節(jié)點,而后面的節(jié)點由每個reference節(jié)點自己通過next來保持即可.
Reference狀態(tài)值
每個引用對象都有相應(yīng)的狀態(tài)描述,即描述自己以及包裝的對象當前處于一個什么樣的狀態(tài),以方便進行查詢,定位或處理.
1、Active:活動狀態(tài),即相應(yīng)的對象為強引用狀態(tài),還沒有被回收,這個狀態(tài)下對象不會放到queue當中.在這個狀態(tài)下next為null,queue為定義時所引用的queue.
2、Pending:準備放入queue當中,在這個狀態(tài)下要處理的對象將挨個地排隊放到queue當中.在這個時間窗口期,相應(yīng)的對象為pending狀態(tài).不管什么reference,進入到此狀態(tài)的,即可認為相應(yīng)的此狀態(tài)下,next為自己(由jvm設(shè)置),queue為定義時所引用的queue.
3、Enqueued:相應(yīng)的對象已經(jīng)為待回收,并且相應(yīng)的引用對象已經(jīng)放到queue當中了.準備由外部線程來詢循queue獲取相應(yīng)的數(shù)據(jù).此狀態(tài)下,next為下一個要處理的對象,queue為特殊標識對象ENQUEUED.
4、Inactive:即此對象已經(jīng)由外部從queue中獲取到,并且已經(jīng)處理掉了.即意味著此引用對象可以被回收,并且對內(nèi)部封裝的對象也可以被回收掉了(實際的回收運行取決于clear動作是否被調(diào)用).可以理解為進入到此狀態(tài)的肯定是應(yīng)該被回收掉的.
jvm并不需要定義狀態(tài)值來判斷相應(yīng)引用的狀態(tài)處于哪個狀態(tài),只需要通過計算next和queue即可進行判斷.
四、ReferenceQueue#head
始終保存當前隊列中最新要被處理的節(jié)點,可以認為queue為一個后進先出的隊列.當新的節(jié)點進入時,采取以下的邏輯
newE.next = head;head=newE;
然后,在獲取的時候,采取相應(yīng)的邏輯
tmp = head;head=tmp.next;return tmp;
五、Reference#next
即描述當前引用節(jié)點所存儲的下一個即將被處理的節(jié)點.但next僅在放到queue中才會有意義.為了描述相應(yīng)的狀態(tài)值,在放到隊列當中后,其queue就不會再引用這個隊列了.而是引用一個特殊的ENQUEUED.因為已經(jīng)放到隊列當中,并且不會再次放到隊列當中.
六、Reference#referent
即描述當前引用所引用的實際對象,正如在注解中所述,其會被仔細地處理.即此什么什么時候會被回收,如果一旦被回收,則會直接置為null,而外部程序可通過通過引用對象本身(而不是referent)了解到,回收行為的產(chǎn)生.
七、ReferenceQueue#enqueue 待處理引用入隊
此過程即在reference對象從active->pending->enqued的過程. 此方法為處理pending狀態(tài)的對象為enqued狀態(tài).相應(yīng)的過程即為之前的邏輯,即將一個節(jié)點入隊操作,相應(yīng)的代碼如下所示.
r.queue = ENQUEUED; r.next = (head == null) ? r : head; head = r; queueLength++; lock.notifyAll();
最后的nitify即通知外部程序之前阻塞在當前隊列之上的情況.(即之前一直沒有拿到待處理的對象)
八、Reference#tryHandlePending
即處理reference對象從active到pending狀態(tài)的變化.在Reference對象內(nèi)部,有一個static字段,其相應(yīng)的聲明如下:
/* 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;
可以理解為jvm在gc時會將要處理的對象放到這個靜態(tài)字段上面.同時,另一個字段discovered,表示要處理的對象的下一個對象.即可以理解要處理的對象也是一個鏈表,通過discovered進行排隊,這邊只需要不停地拿到pending,然后再通過discovered不斷地拿到下一個對象即可.因為這個pending對象,兩個線程都可能訪問,因此需要加鎖處理.
相應(yīng)的處理過程如下所示:
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; } //將處理對象入隊,即進入到enqued狀態(tài) ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r);
九、Reference#clear
清除引用對象所引用的原對象,這樣通過get()方法就不能再訪問到原對象了.從相應(yīng)的設(shè)計思路來說,既然都進入到queue對象里面,就表示相應(yīng)的對象需要被回收了,因為沒有再訪問原對象的必要.此方法不會由JVM調(diào)用,而jvm是直接通過字段操作清除相應(yīng)的引用,其具體實現(xiàn)與當前方法相一致.
clear的語義就是將referent置null.
WeakReference對象進入到queue之后,相應(yīng)的referent為null.
SoftReference對象,如果對象在內(nèi)存足夠時,不會進入到queue,自然相應(yīng)的reference不會為null.如果需要被處理(內(nèi)存不夠或其它策略),則置相應(yīng)的referent為null,然后進入到queue.
FinalReference對象,因為需要調(diào)用其finalize對象,因此其reference即使入queue,其referent也不會為null,即不會clear掉.
PhantomReference對象,因為本身get實現(xiàn)為返回null.因此clear的作用不是很大.因為不管enqueue還是沒有,都不會清除掉.
十、ReferenceHandler enqueue線程
上面提到j(luò)vm會將要處理的對象設(shè)置到pending對象當中,因此肯定有一個線程來進行不斷的enqueue操作,此線程即引用處理器線程,其優(yōu)先級為MAX_PRIORITY,即最高.相應(yīng)的啟動過程為靜態(tài)初始化創(chuàng)建,可以理解為當任何使用到Reference對象或類時,此線程即會被創(chuàng)建并啟動.相應(yīng)的代碼如下所示:
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(); }
其優(yōu)先級最高,可以理解為需要不斷地處理引用對象.在通過jstack打印運行線程時,相應(yīng)的Reference Handler即是指在這里初始化的線程,如下所示:
十一、JVM相關(guān)
在上述的各個處理點當中,都與JVM的回收過程相關(guān).即認為gc流程會與相應(yīng)的reference協(xié)同工作.如使用cms收集器,在上述的整個流程當中,涉及到preclean過程,也涉及到softReference的重新標記處理等,同時對reference對象的各種處理也需要與具體的類型相關(guān)進行協(xié)作.相應(yīng)的JVM處理,采用C++代碼,因此需要好好地再理一下.
十二、總結(jié)
與finalReference對象相同,整個reference和referenceQueue都是一組協(xié)同工作的處理組,為保證不同的引用語義,通過與jvm gc相關(guān)的流程一起作用,最終實現(xiàn)不同場景,不同引用級別的處理.
另外,由于直接使用referenceQueue,再加上開啟線程去監(jiān)控queue太過麻煩和復(fù)雜.可以參考由google guava實現(xiàn)的 FinalizableReferenceQueue 以及相應(yīng)的FinalizableReference對象.可以簡化一點點處理過程.以上就是這篇文章的全部內(nèi)容,希望對大家的學習或者工作帶來一定的幫助。
相關(guān)文章
Java實現(xiàn)商品管理系統(tǒng)代碼實例講解
這篇文章主要介紹了Java實現(xiàn)商品管理系統(tǒng)代碼實例講解,文中代碼實例講解的很清楚,有需要的同學可以借鑒參考下2021-02-02Java框架搭建之Maven、Mybatis、Spring MVC整合搭建(圖文)
這篇文章主要介紹了Java框架搭建之Maven、Mybatis、Spring MVC整合搭建(圖文),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12Springboot獲取前端反饋信息并存入數(shù)據(jù)庫的實現(xiàn)代碼
這篇文章主要介紹了Springboot獲取前端反饋信息并存入數(shù)據(jù)庫的實現(xiàn)代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03SpringBoot中使用Jsoup爬取網(wǎng)站數(shù)據(jù)的方法
這篇文章主要介紹了SpringBoot中使用Jsoup爬取網(wǎng)站數(shù)據(jù)的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-06-06Java concurrency之集合_動力節(jié)點Java學院整理
Java集合主體內(nèi)容包括Collection集合和Map類;而Collection集合又可以劃分為List(隊列)和Set(集合),有需要的小伙伴可以參考下2017-06-06SpringBoot發(fā)現(xiàn)最新版Druid重大問題(坑)
這篇文章主要介紹了SpringBoot發(fā)現(xiàn)最新版Druid重大問題(坑),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09Java面試題及答案集錦(基礎(chǔ)題122道,代碼題19道)
本文是小編收集整理的關(guān)于java基礎(chǔ)面試題及答案集錦,基礎(chǔ)題目有122道,代碼題目有19道,非常不錯,值得收藏,需要的朋友參考下2017-01-01