詳解java中Reference的實(shí)現(xiàn)與相應(yīng)的執(zhí)行過(guò)程
一、Reference類型(除強(qiáng)引用)
可以理解為Reference的直接子類都是由jvm定制化處理的,因此在代碼中直接繼承于Reference類型沒(méi)有任何作用.只能繼承于它的子類,相應(yīng)的子類類型包括以下幾種.(忽略沒(méi)有在java中使用的,如jnireference)
SoftReference
WeakReference
FinalReference
PhantomReference
上面的引用類型在相應(yīng)的javadoc中也有提及.FinalReference專門(mén)為finalize方法設(shè)計(jì),另外幾個(gè)也有特定的應(yīng)用場(chǎng)景.其中softReference用在內(nèi)存相關(guān)的緩存當(dāng)中,weakReference用在與回收相關(guān)的大多數(shù)場(chǎng)景.phantomReference用在與包裝對(duì)象回收回調(diào)場(chǎng)景當(dāng)中(比如資源泄漏檢測(cè)).
可以直接在ide中查看幾個(gè)類型的子類信息,即可了解在大多數(shù)框架中,都是通過(guò)繼承相應(yīng)的類型用在什么場(chǎng)景當(dāng)中,以便于我們實(shí)際進(jìn)行選型處理.
二、Reference構(gòu)造函數(shù)
其內(nèi)部提供2個(gè)構(gòu)造函數(shù),一個(gè)帶queue,一個(gè)不帶queue.其中queue的意義在于,我們可以在外部對(duì)這個(gè)queue進(jìn)行監(jiān)控.即如果有對(duì)象即將被回收,那么相應(yīng)的reference對(duì)象就會(huì)被放到這個(gè)queue里.我們拿到reference,就可以再作一些事務(wù).
而如果不帶的話,就只有不斷地輪訓(xùn)reference對(duì)象,通過(guò)判斷里面的get是否返回null(phantomReference對(duì)象不能這樣作,其get始終返回null,因此它只有帶queue的構(gòu)造函數(shù)).這兩種方法均有相應(yīng)的使用場(chǎng)景,取決于實(shí)際的應(yīng)用.如weakHashMap中就選擇去查詢queue的數(shù)據(jù),來(lái)判定是否有對(duì)象將被回收.而ThreadLocalMap,則采用判斷get()是否為null來(lái)作處理.
相應(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隊(duì)列,即可以理解為不需要對(duì)其隊(duì)列中的數(shù)據(jù)作任何處理的隊(duì)列.并且其內(nèi)部也不會(huì)存取任何數(shù)據(jù).
在上面的對(duì)象中,referent表示其引用的對(duì)象,即我們?cè)跇?gòu)造的時(shí)候,需要被包裝在其中的對(duì)象.對(duì)象即將被回收的定義即此對(duì)象除了被reference引用之外沒(méi)有其它引用了(并非確實(shí)沒(méi)有被引用,而是gcRoot可達(dá)性不可達(dá),以避免循環(huán)引用的問(wèn)題).
queue即是對(duì)象即被回收時(shí)所要通知的隊(duì)列,當(dāng)對(duì)象即被回收時(shí),整個(gè)reference對(duì)象(而不是被回收的對(duì)象)會(huì)被放到queue里面,然后外部程序即可通過(guò)監(jiān)控這個(gè)queue拿到相應(yīng)的數(shù)據(jù)了.
三、ReferenceQueue及Reference引用鏈
這里的queue名義上是一個(gè)隊(duì)列,但實(shí)際內(nèi)部并非有實(shí)際的存儲(chǔ)結(jié)構(gòu),它的存儲(chǔ)是依賴于內(nèi)部節(jié)點(diǎn)之間的關(guān)系來(lái)表達(dá).可以理解為queue是一個(gè)類似于鏈表的結(jié)構(gòu),這里的節(jié)點(diǎn)其實(shí)就是reference本身.可以理解為queue為一個(gè)鏈表的容器,其自己僅存儲(chǔ)當(dāng)前的head節(jié)點(diǎn),而后面的節(jié)點(diǎn)由每個(gè)reference節(jié)點(diǎn)自己通過(guò)next來(lái)保持即可.
Reference狀態(tài)值
每個(gè)引用對(duì)象都有相應(yīng)的狀態(tài)描述,即描述自己以及包裝的對(duì)象當(dāng)前處于一個(gè)什么樣的狀態(tài),以方便進(jìn)行查詢,定位或處理.
1、Active:活動(dòng)狀態(tài),即相應(yīng)的對(duì)象為強(qiáng)引用狀態(tài),還沒(méi)有被回收,這個(gè)狀態(tài)下對(duì)象不會(huì)放到queue當(dāng)中.在這個(gè)狀態(tài)下next為null,queue為定義時(shí)所引用的queue.
2、Pending:準(zhǔn)備放入queue當(dāng)中,在這個(gè)狀態(tài)下要處理的對(duì)象將挨個(gè)地排隊(duì)放到queue當(dāng)中.在這個(gè)時(shí)間窗口期,相應(yīng)的對(duì)象為pending狀態(tài).不管什么reference,進(jìn)入到此狀態(tài)的,即可認(rèn)為相應(yīng)的此狀態(tài)下,next為自己(由jvm設(shè)置),queue為定義時(shí)所引用的queue.
3、Enqueued:相應(yīng)的對(duì)象已經(jīng)為待回收,并且相應(yīng)的引用對(duì)象已經(jīng)放到queue當(dāng)中了.準(zhǔn)備由外部線程來(lái)詢循queue獲取相應(yīng)的數(shù)據(jù).此狀態(tài)下,next為下一個(gè)要處理的對(duì)象,queue為特殊標(biāo)識(shí)對(duì)象ENQUEUED.
4、Inactive:即此對(duì)象已經(jīng)由外部從queue中獲取到,并且已經(jīng)處理掉了.即意味著此引用對(duì)象可以被回收,并且對(duì)內(nèi)部封裝的對(duì)象也可以被回收掉了(實(shí)際的回收運(yùn)行取決于clear動(dòng)作是否被調(diào)用).可以理解為進(jìn)入到此狀態(tài)的肯定是應(yīng)該被回收掉的.
jvm并不需要定義狀態(tài)值來(lái)判斷相應(yīng)引用的狀態(tài)處于哪個(gè)狀態(tài),只需要通過(guò)計(jì)算next和queue即可進(jìn)行判斷.
四、ReferenceQueue#head
始終保存當(dāng)前隊(duì)列中最新要被處理的節(jié)點(diǎn),可以認(rèn)為queue為一個(gè)后進(jìn)先出的隊(duì)列.當(dāng)新的節(jié)點(diǎn)進(jìn)入時(shí),采取以下的邏輯
newE.next = head;head=newE;
然后,在獲取的時(shí)候,采取相應(yīng)的邏輯
tmp = head;head=tmp.next;return tmp;
五、Reference#next
即描述當(dāng)前引用節(jié)點(diǎn)所存儲(chǔ)的下一個(gè)即將被處理的節(jié)點(diǎn).但next僅在放到queue中才會(huì)有意義.為了描述相應(yīng)的狀態(tài)值,在放到隊(duì)列當(dāng)中后,其queue就不會(huì)再引用這個(gè)隊(duì)列了.而是引用一個(gè)特殊的ENQUEUED.因?yàn)橐呀?jīng)放到隊(duì)列當(dāng)中,并且不會(huì)再次放到隊(duì)列當(dāng)中.
六、Reference#referent
即描述當(dāng)前引用所引用的實(shí)際對(duì)象,正如在注解中所述,其會(huì)被仔細(xì)地處理.即此什么什么時(shí)候會(huì)被回收,如果一旦被回收,則會(huì)直接置為null,而外部程序可通過(guò)通過(guò)引用對(duì)象本身(而不是referent)了解到,回收行為的產(chǎn)生.
七、ReferenceQueue#enqueue 待處理引用入隊(duì)
此過(guò)程即在reference對(duì)象從active->pending->enqued的過(guò)程. 此方法為處理pending狀態(tài)的對(duì)象為enqued狀態(tài).相應(yīng)的過(guò)程即為之前的邏輯,即將一個(gè)節(jié)點(diǎn)入隊(duì)操作,相應(yīng)的代碼如下所示.
r.queue = ENQUEUED; r.next = (head == null) ? r : head; head = r; queueLength++; lock.notifyAll();
最后的nitify即通知外部程序之前阻塞在當(dāng)前隊(duì)列之上的情況.(即之前一直沒(méi)有拿到待處理的對(duì)象)
八、Reference#tryHandlePending
即處理reference對(duì)象從active到pending狀態(tài)的變化.在Reference對(duì)象內(nèi)部,有一個(gè)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時(shí)會(huì)將要處理的對(duì)象放到這個(gè)靜態(tài)字段上面.同時(shí),另一個(gè)字段discovered,表示要處理的對(duì)象的下一個(gè)對(duì)象.即可以理解要處理的對(duì)象也是一個(gè)鏈表,通過(guò)discovered進(jìn)行排隊(duì),這邊只需要不停地拿到pending,然后再通過(guò)discovered不斷地拿到下一個(gè)對(duì)象即可.因?yàn)檫@個(gè)pending對(duì)象,兩個(gè)線程都可能訪問(wèn),因此需要加鎖處理.
相應(yīng)的處理過(guò)程如下所示:
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;
}
//將處理對(duì)象入隊(duì),即進(jìn)入到enqued狀態(tài)
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
九、Reference#clear
清除引用對(duì)象所引用的原對(duì)象,這樣通過(guò)get()方法就不能再訪問(wèn)到原對(duì)象了.從相應(yīng)的設(shè)計(jì)思路來(lái)說(shuō),既然都進(jìn)入到queue對(duì)象里面,就表示相應(yīng)的對(duì)象需要被回收了,因?yàn)闆](méi)有再訪問(wèn)原對(duì)象的必要.此方法不會(huì)由JVM調(diào)用,而jvm是直接通過(guò)字段操作清除相應(yīng)的引用,其具體實(shí)現(xiàn)與當(dāng)前方法相一致.
clear的語(yǔ)義就是將referent置null.
WeakReference對(duì)象進(jìn)入到queue之后,相應(yīng)的referent為null.
SoftReference對(duì)象,如果對(duì)象在內(nèi)存足夠時(shí),不會(huì)進(jìn)入到queue,自然相應(yīng)的reference不會(huì)為null.如果需要被處理(內(nèi)存不夠或其它策略),則置相應(yīng)的referent為null,然后進(jìn)入到queue.
FinalReference對(duì)象,因?yàn)樾枰{(diào)用其finalize對(duì)象,因此其reference即使入queue,其referent也不會(huì)為null,即不會(huì)clear掉.
PhantomReference對(duì)象,因?yàn)楸旧韌et實(shí)現(xiàn)為返回null.因此clear的作用不是很大.因?yàn)椴还躤nqueue還是沒(méi)有,都不會(huì)清除掉.
十、ReferenceHandler enqueue線程
上面提到j(luò)vm會(huì)將要處理的對(duì)象設(shè)置到pending對(duì)象當(dāng)中,因此肯定有一個(gè)線程來(lái)進(jìn)行不斷的enqueue操作,此線程即引用處理器線程,其優(yōu)先級(jí)為MAX_PRIORITY,即最高.相應(yīng)的啟動(dòng)過(guò)程為靜態(tài)初始化創(chuàng)建,可以理解為當(dāng)任何使用到Reference對(duì)象或類時(shí),此線程即會(huì)被創(chuàng)建并啟動(dò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)先級(jí)最高,可以理解為需要不斷地處理引用對(duì)象.在通過(guò)jstack打印運(yùn)行線程時(shí),相應(yīng)的Reference Handler即是指在這里初始化的線程,如下所示:

十一、JVM相關(guān)
在上述的各個(gè)處理點(diǎn)當(dāng)中,都與JVM的回收過(guò)程相關(guān).即認(rèn)為gc流程會(huì)與相應(yīng)的reference協(xié)同工作.如使用cms收集器,在上述的整個(gè)流程當(dāng)中,涉及到preclean過(guò)程,也涉及到softReference的重新標(biāo)記處理等,同時(shí)對(duì)reference對(duì)象的各種處理也需要與具體的類型相關(guān)進(jìn)行協(xié)作.相應(yīng)的JVM處理,采用C++代碼,因此需要好好地再理一下.
十二、總結(jié)
與finalReference對(duì)象相同,整個(gè)reference和referenceQueue都是一組協(xié)同工作的處理組,為保證不同的引用語(yǔ)義,通過(guò)與jvm gc相關(guān)的流程一起作用,最終實(shí)現(xiàn)不同場(chǎng)景,不同引用級(jí)別的處理.
另外,由于直接使用referenceQueue,再加上開(kāi)啟線程去監(jiān)控queue太過(guò)麻煩和復(fù)雜.可以參考由google guava實(shí)現(xiàn)的 FinalizableReferenceQueue 以及相應(yīng)的FinalizableReference對(duì)象.可以簡(jiǎn)化一點(diǎn)點(diǎn)處理過(guò)程.以上就是這篇文章的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)或者工作帶來(lái)一定的幫助。
相關(guān)文章
關(guān)于解決雪花算法生成的ID傳輸前端后精度丟失問(wèn)題
這篇文章主要介紹了關(guān)于解決雪花算法生成的ID傳輸前端后精度丟失問(wèn)題,雪花算法生成的ID傳輸?shù)角岸藭r(shí),會(huì)出現(xiàn)后三位精度丟失,本文提供了解決思路,需要的朋友可以參考下2023-03-03
Java實(shí)用小技能之快速創(chuàng)建List常用幾種方式
java集合可以說(shuō)無(wú)論是面試、刷題還是工作中都是非常常用的,下面這篇文章主要給大家介紹了關(guān)于Java實(shí)用小技能之快速創(chuàng)建List常用的幾種方式,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12
Spring security用戶URL權(quán)限FilterSecurityInterceptor使用解析
這篇文章主要介紹了Spring security用戶URL權(quán)限FilterSecurityInterceptor使用解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Java8特性之用Stream流代替For循環(huán)操作詳解
這篇文章主要介紹了Stream流代替For循環(huán)進(jìn)行輸出,這樣可以使代碼更簡(jiǎn)潔,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-09-09
SpringBoot項(xiàng)目中Druid自動(dòng)登錄功能實(shí)現(xiàn)
Druid是Java語(yǔ)言中最好的數(shù)據(jù)庫(kù)連接池,Druid能夠提供強(qiáng)大的監(jiān)控和擴(kuò)展功能,這篇文章主要介紹了SpringBoot項(xiàng)目中Druid自動(dòng)登錄功能實(shí)現(xiàn),需要的朋友可以參考下2024-08-08
Java源碼解析之Gateway請(qǐng)求轉(zhuǎn)發(fā)
今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著Gateway請(qǐng)求轉(zhuǎn)發(fā)展開(kāi),文中有非常詳細(xì)介紹及代碼示例,需要的朋友可以參考下2021-06-06
Elasticsearch開(kāi)發(fā)AtomicArray使用示例探究
這篇文章主要為大家介紹了Elasticsearch AtomicArray使用示例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Java利用自定義注解、反射實(shí)現(xiàn)簡(jiǎn)單BaseDao實(shí)例
下面小編就為大家?guī)?lái)一篇Java利用自定義注解、反射實(shí)現(xiàn)簡(jiǎn)單BaseDao實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08

