您好,登錄后才能下訂單哦!
Java中一共有4種引用類型(其實(shí)還有一些其他的引用類型比如FinalReference):強(qiáng)引用、軟引用、弱引用、虛引用。其中強(qiáng)引用就是我們經(jīng)常使用的Object a = new Object();
?這樣的形式,在Java中并沒(méi)有對(duì)應(yīng)的Reference類。
本篇文章主要是分析軟引用、弱引用、虛引用的實(shí)現(xiàn),這三種引用類型都是繼承于Reference這個(gè)類,主要邏輯也在Reference中。
在分析前,先拋幾個(gè)問(wèn)題?
1.網(wǎng)上大多數(shù)文章對(duì)于軟引用的介紹是:在內(nèi)存不足的時(shí)候才會(huì)被回收,那內(nèi)存不足是怎么定義的?什么才叫內(nèi)存不足?
2.網(wǎng)上大多數(shù)文章對(duì)于虛引用的介紹是:形同虛設(shè),虛引用并不會(huì)決定對(duì)象的生命周期。主要用來(lái)跟蹤對(duì)象被垃圾回收器回收的活動(dòng)。真的是這樣嗎?
3.虛引用在Jdk中有哪些場(chǎng)景下用到了呢?
我們先看下Reference.java
中的幾個(gè)字段
public?abstract?class?Reference<T>?{ ????//引用的對(duì)象 ????private?T?referent;???????? //回收隊(duì)列,由使用者在Reference的構(gòu)造函數(shù)中指定 ????volatile?ReferenceQueue<??super?T>?queue; ? //當(dāng)該引用被加入到queue中的時(shí)候,該字段被設(shè)置為queue中的下一個(gè)元素,以形成鏈表結(jié)構(gòu) ????volatile?Reference?next; ????//在GC時(shí),JVM底層會(huì)維護(hù)一個(gè)叫DiscoveredList的鏈表,存放的是Reference對(duì)象,discovered字段指向的就是鏈表中的下一個(gè)元素,由JVM設(shè)置 ????transient?private?Reference<T>?discovered;?? //進(jìn)行線程同步的鎖對(duì)象 ????static?private?class?Lock?{?} ????private?static?Lock?lock?=?new?Lock(); //等待加入queue的Reference對(duì)象,在GC時(shí)由JVM設(shè)置,會(huì)有一個(gè)java層的線程(ReferenceHandler)源源不斷的從pending中提取元素加入到queue ????private?static?Reference<Object>?pending?=?null; }
一個(gè)Reference對(duì)象的生命周期如下:
主要分為Native層和Java層兩個(gè)部分。
Native層在GC時(shí)將需要被回收的Reference對(duì)象加入到DiscoveredList中(代碼在referenceProcessor.cpp
中process_discovered_references
方法),然后將DiscoveredList的元素移動(dòng)到PendingList中(代碼在referenceProcessor.cpp
中enqueue_discovered_ref_helper
方法),PendingList的隊(duì)首就是Reference類中的pending對(duì)象。
看看Java層的代碼
private?static?class?ReferenceHandler?extends?Thread?{ ????? ... ????????public?void?run()?{ ????????????while?(true)?{ ????????????????tryHandlePending(true); ????????????} ????????} ??}? static?boolean?tryHandlePending(boolean?waitForNotify)?{ ????????Reference<Object>?r; ????????Cleaner?c; ????????try?{ ????????????synchronized?(lock)?{ ????????????????if?(pending?!=?null)?{ ????????????????????r?=?pending; ????????????????? //如果是Cleaner對(duì)象,則記錄下來(lái),下面做特殊處理 ????????????????????c?=?r?instanceof?Cleaner???(Cleaner)?r?:?null; ????????????????????//指向PendingList的下一個(gè)對(duì)象 ????????????????????pending?=?r.discovered; ????????????????????r.discovered?=?null; ????????????????}?else?{ ???????????????????//如果pending為null就先等待,當(dāng)有對(duì)象加入到PendingList中時(shí),jvm會(huì)執(zhí)行notify ????????????????????if?(waitForNotify)?{ ????????????????????????lock.wait(); ????????????????????} ????????????????????//?retry?if?waited ????????????????????return?waitForNotify; ????????????????} ????????????} ????????}? ????????... ????????//?如果時(shí)CLeaner對(duì)象,則調(diào)用clean方法進(jìn)行資源回收 ????????if?(c?!=?null)?{ ????????????c.clean(); ????????????return?true; ????????} //將Reference加入到ReferenceQueue,開(kāi)發(fā)者可以通過(guò)從ReferenceQueue中poll元素感知到對(duì)象被回收的事件。 ????????ReferenceQueue<??super?Object>?q?=?r.queue; ????????if?(q?!=?ReferenceQueue.NULL)?q.enqueue(r); ????????return?true; ?}
流程比較簡(jiǎn)單:就是源源不斷的從PendingList中提取出元素,然后將其加入到ReferenceQueue中去,開(kāi)發(fā)者可以通過(guò)從ReferenceQueue中poll元素感知到對(duì)象被回收的事件。
另外需要注意的是,對(duì)于Cleaner類型(繼承自虛引用)的對(duì)象會(huì)有額外的處理:在其指向的對(duì)象被回收時(shí),會(huì)調(diào)用clean方法,該方法主要是用來(lái)做對(duì)應(yīng)的資源回收,在堆外內(nèi)存DirectByteBuffer中就是用Cleaner進(jìn)行堆外內(nèi)存的回收,這也是虛引用在java中的典型應(yīng)用。
看完了Reference的實(shí)現(xiàn),再看看幾個(gè)實(shí)現(xiàn)類里,各自有什么不同。
public?class?SoftReference<T>?extends?Reference<T>?{ ???? ????static?private?long?clock; ???? ????private?long?timestamp; ??? ????public?SoftReference(T?referent)?{ ????????super(referent); ????????this.timestamp?=?clock; ????} ? ????public?SoftReference(T?referent,?ReferenceQueue<??super?T>?q)?{ ????????super(referent,?q); ????????this.timestamp?=?clock; ????} ????public?T?get()?{ ????????T?o?=?super.get(); ????????if?(o?!=?null?&&?this.timestamp?!=?clock) ????????????this.timestamp?=?clock; ????????return?o; ????} }
軟引用的實(shí)現(xiàn)很簡(jiǎn)單,就多了兩個(gè)字段:clock
和timestamp
。clock
是個(gè)靜態(tài)變量,每次GC時(shí)都會(huì)將該字段設(shè)置成當(dāng)前時(shí)間。timestamp
字段則會(huì)在每次調(diào)用get方法時(shí)將其賦值為clock
(如果不相等且對(duì)象沒(méi)被回收)。
那這兩個(gè)字段的作用是什么呢?這和軟引用在內(nèi)存不夠的時(shí)候才被回收,又有什么關(guān)系呢?
這些還得看JVM的源碼才行,因?yàn)闆Q定對(duì)象是否需要被回收都是在GC中實(shí)現(xiàn)的。
size_t ReferenceProcessor::process_discovered_reflist( ??DiscoveredList???????????????refs_lists[], ??ReferencePolicy*?????????????policy, ??bool?????????????????????????clear_referent, ??BoolObjectClosure*???????????is_alive, ??OopClosure*??????????????????keep_alive, ??VoidClosure*?????????????????complete_gc, ??AbstractRefProcTaskExecutor*?task_executor) { ?... ???//還記得上文提到過(guò)的DiscoveredList嗎?refs_lists就是DiscoveredList。 ???//對(duì)于DiscoveredList的處理分為幾個(gè)階段,SoftReference的處理就在第一階段 ?... ??????for?(uint?i?=?0;?i?<?_max_num_q;?i++)?{ ????????process_phase1(refs_lists[i],?policy, ???????????????????????is_alive,?keep_alive,?complete_gc); ??????} ?... } //該階段的主要目的就是當(dāng)內(nèi)存足夠時(shí),將對(duì)應(yīng)的SoftReference從refs_list中移除。 void ReferenceProcessor::process_phase1(DiscoveredList&????refs_list, ???????????????????????????????????ReferencePolicy*???policy, ???????????????????????????????????BoolObjectClosure*?is_alive, ???????????????????????????????????OopClosure*????????keep_alive, ???????????????????????????????????VoidClosure*???????complete_gc)?{ ?? ??DiscoveredListIterator?iter(refs_list,?keep_alive,?is_alive); ??//?Decide?which?softly?reachable?refs?should?be?kept?alive. ??while?(iter.has_next())?{ ????iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic()?/*?allow_null_referent?*/)); ????//判斷引用的對(duì)象是否存活 ????bool?referent_is_dead?=?(iter.referent()?!=?NULL)?&&?!iter.is_referent_alive(); ????//如果引用的對(duì)象已經(jīng)不存活了,則會(huì)去調(diào)用對(duì)應(yīng)的ReferencePolicy判斷該對(duì)象是不時(shí)要被回收 ????if?(referent_is_dead?&& ????????!policy->should_clear_reference(iter.obj(),?_soft_ref_timestamp_clock))?{ ??????if?(TraceReferenceGC)?{ ????????gclog_or_tty->print_cr("Dropping?reference?("?INTPTR_FORMAT?":?%s"??")?by?policy", ???????????????????????????????(void?*)iter.obj(),?iter.obj()->klass()->internal_name()); ??????} ??????//?Remove?Reference?object?from?list ??????iter.remove(); ??????//?Make?the?Reference?object?active?again ??????iter.make_active(); ??????//?keep?the?referent?around ??????iter.make_referent_alive(); ??????iter.move_to_next(); ????}?else?{ ??????iter.next(); ????} ??} ?... }
refs_lists
中存放了本次GC發(fā)現(xiàn)的某種引用類型(虛引用、軟引用、弱引用等),而process_discovered_reflist
方法的作用就是將不需要被回收的對(duì)象從refs_lists
移除掉,refs_lists
最后剩下的元素全是需要被回收的元素,最后會(huì)將其第一個(gè)元素賦值給上文提到過(guò)的Reference.java#pending
字段。
ReferencePolicy一共有4種實(shí)現(xiàn):NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。其中NeverClearPolicy永遠(yuǎn)返回false,代表永遠(yuǎn)不回收SoftReference,在JVM中該類沒(méi)有被使用,AlwaysClearPolicy則永遠(yuǎn)返回true,在referenceProcessor.hpp#setup
方法中中可以設(shè)置policy為AlwaysClearPolicy,至于什么時(shí)候會(huì)用到AlwaysClearPolicy,大家有興趣可以自行研究。
LRUCurrentHeapPolicy和LRUMaxHeapPolicy的should_clear_reference方法則是完全相同:
bool?LRUMaxHeapPolicy::should_clear_reference(oop?p, ?????????????????????????????????????????????jlong?timestamp_clock)?{ ??jlong?interval?=?timestamp_clock?-?java_lang_ref_SoftReference::timestamp(p); ??assert(interval?>=?0,?"Sanity?check"); ??//?The?interval?will?be?zero?if?the?ref?was?accessed?since?the?last?scavenge/gc. ??if(interval?<=?_max_interval)?{ ????return?false; ??} ??return?true; }
timestamp_clock
就是SoftReference的靜態(tài)字段clock
,java_lang_ref_SoftReference::timestamp(p)
對(duì)應(yīng)是字段timestamp
。如果上次GC后有調(diào)用SoftReference#get
,interval
值為0,否則為若干次GC之間的時(shí)間差。
_max_interval
則代表了一個(gè)臨界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy兩種策略中有差異。
void?LRUCurrentHeapPolicy::setup()?{ ??_max_interval?=?(Universe::get_heap_free_at_last_gc()?/?M)?*?SoftRefLRUPolicyMSPerMB; ??assert(_max_interval?>=?0,"Sanity?check"); } void?LRUMaxHeapPolicy::setup()?{ ??size_t?max_heap?=?MaxHeapSize; ??max_heap?-=?Universe::get_heap_used_at_last_gc(); ??max_heap?/=?M; ??_max_interval?=?max_heap?*?SoftRefLRUPolicyMSPerMB; ??assert(_max_interval?>=?0,"Sanity?check"); }
其中SoftRefLRUPolicyMSPerMB
默認(rèn)為1000,前者的計(jì)算方法和上次GC后可用堆大小有關(guān),后者計(jì)算方法和(堆大小-上次gc時(shí)堆使用大小)有關(guān)。
看到這里你就知道SoftReference到底什么時(shí)候被被回收了,它和使用的策略(默認(rèn)應(yīng)該是LRUCurrentHeapPolicy),堆可用大小,該SoftReference上一次調(diào)用get方法的時(shí)間都有關(guān)系。
public?class?WeakReference<T>?extends?Reference<T>?{ ????public?WeakReference(T?referent)?{ ????????super(referent); ????} ????public?WeakReference(T?referent,?ReferenceQueue<??super?T>?q)?{ ????????super(referent,?q); ????} }
可以看到WeakReference在Java層只是繼承了Reference,沒(méi)有做任何的改動(dòng)。那referent字段是什么時(shí)候被置為null的呢?要搞清楚這個(gè)問(wèn)題我們?cè)倏聪律衔奶岬竭^(guò)的process_discovered_reflist
方法:
size_t ReferenceProcessor::process_discovered_reflist( ??DiscoveredList???????????????refs_lists[], ??ReferencePolicy*?????????????policy, ??bool?????????????????????????clear_referent, ??BoolObjectClosure*???????????is_alive, ??OopClosure*??????????????????keep_alive, ??VoidClosure*?????????????????complete_gc, ??AbstractRefProcTaskExecutor*?task_executor) { ?... ??//Phase?1:將所有不存活但是還不能被回收的軟引用從refs_lists中移除(只有refs_lists為軟引用的時(shí)候,這里policy才不為null) ??if?(policy?!=?NULL)?{ ????if?(mt_processing)?{ ??????RefProcPhase1Task?phase1(*this,?refs_lists,?policy,?true?/*marks_oops_alive*/); ??????task_executor->execute(phase1); ????}?else?{ ??????for?(uint?i?=?0;?i?<?_max_num_q;?i++)?{ ????????process_phase1(refs_lists[i],?policy, ???????????????????????is_alive,?keep_alive,?complete_gc); ??????} ????} ??}?else?{?//?policy?==?NULL ????assert(refs_lists?!=?_discoveredSoftRefs, ???????????"Policy?must?be?specified?for?soft?references."); ??} ??//?Phase?2: ??//?移除所有指向?qū)ο筮€存活的引用 ??if?(mt_processing)?{ ????RefProcPhase2Task?phase2(*this,?refs_lists,?!discovery_is_atomic()?/*marks_oops_alive*/); ????task_executor->execute(phase2); ??}?else?{ ????for?(uint?i?=?0;?i?<?_max_num_q;?i++)?{ ??????process_phase2(refs_lists[i],?is_alive,?keep_alive,?complete_gc); ????} ??} ??//?Phase?3: ??//?根據(jù)clear_referent的值決定是否將不存活對(duì)象回收 ??if?(mt_processing)?{ ????RefProcPhase3Task?phase3(*this,?refs_lists,?clear_referent,?true?/*marks_oops_alive*/); ????task_executor->execute(phase3); ??}?else?{ ????for?(uint?i?=?0;?i?<?_max_num_q;?i++)?{ ??????process_phase3(refs_lists[i],?clear_referent, ?????????????????????is_alive,?keep_alive,?complete_gc); ????} ??} ??return?total_list_count; } void ReferenceProcessor::process_phase3(DiscoveredList&????refs_list, ???????????????????????????????????bool???????????????clear_referent, ???????????????????????????????????BoolObjectClosure*?is_alive, ???????????????????????????????????OopClosure*????????keep_alive, ???????????????????????????????????VoidClosure*???????complete_gc)?{ ??ResourceMark?rm; ??DiscoveredListIterator?iter(refs_list,?keep_alive,?is_alive); ??while?(iter.has_next())?{ ????iter.update_discovered(); ????iter.load_ptrs(DEBUG_ONLY(false?/*?allow_null_referent?*/)); ????if?(clear_referent)?{ ??????//?NULL?out?referent?pointer ??????//將Reference的referent字段置為null,之后會(huì)被GC回收 ??????iter.clear_referent(); ????}?else?{ ??????//?keep?the?referent?around ??????//標(biāo)記引用的對(duì)象為存活,該對(duì)象在這次GC將不會(huì)被回收 ??????iter.make_referent_alive(); ????} ... ??} ????... }
不管是弱引用還是其他引用類型,將字段referent置null的操作都發(fā)生在process_phase3
中,而具體行為是由clear_referent
的值決定的。而clear_referent
的值則和引用類型相關(guān)。
ReferenceProcessorStats?ReferenceProcessor::process_discovered_references( ??BoolObjectClosure*???????????is_alive, ??OopClosure*??????????????????keep_alive, ??VoidClosure*?????????????????complete_gc, ??AbstractRefProcTaskExecutor*?task_executor, ??GCTimer*?????????????????????gc_timer)?{ ??NOT_PRODUCT(verify_ok_to_handle_reflists()); ... ??//process_discovered_reflist方法的第3個(gè)字段就是clear_referent ??//?Soft?references ??size_t?soft_count?=?0; ??{ ????GCTraceTime?tt("SoftReference",?trace_time,?false,?gc_timer); ????soft_count?= ??????process_discovered_reflist(_discoveredSoftRefs,?_current_soft_ref_policy,?true, ?????????????????????????????????is_alive,?keep_alive,?complete_gc,?task_executor); ??} ??update_soft_ref_master_clock(); ??//?Weak?references ??size_t?weak_count?=?0; ??{ ????GCTraceTime?tt("WeakReference",?trace_time,?false,?gc_timer); ????weak_count?= ??????process_discovered_reflist(_discoveredWeakRefs,?NULL,?true, ?????????????????????????????????is_alive,?keep_alive,?complete_gc,?task_executor); ??} ??//?Final?references ??size_t?final_count?=?0; ??{ ????GCTraceTime?tt("FinalReference",?trace_time,?false,?gc_timer); ????final_count?= ??????process_discovered_reflist(_discoveredFinalRefs,?NULL,?false, ?????????????????????????????????is_alive,?keep_alive,?complete_gc,?task_executor); ??} ??//?Phantom?references ??size_t?phantom_count?=?0; ??{ ????GCTraceTime?tt("PhantomReference",?trace_time,?false,?gc_timer); ????phantom_count?= ??????process_discovered_reflist(_discoveredPhantomRefs,?NULL,?false, ?????????????????????????????????is_alive,?keep_alive,?complete_gc,?task_executor); ??} ... }
可以看到,對(duì)于Soft references和Weak references?clear_referent
字段傳入的都是true,這也符合我們的預(yù)期:對(duì)象不可達(dá)后,引用字段就會(huì)被置為null,然后對(duì)象就會(huì)被回收(對(duì)于軟引用來(lái)說(shuō),如果內(nèi)存足夠的話,在Phase 1,相關(guān)的引用就會(huì)從refs_list中被移除,到Phase 3時(shí)refs_list為空集合)。
但對(duì)于Final references和 Phantom references,clear_referent
字段傳入的是false,也就意味著被這兩種引用類型引用的對(duì)象,如果沒(méi)有其他額外處理,只要Reference對(duì)象還存活,那引用的對(duì)象是不會(huì)被回收的。Final references和對(duì)象是否重寫(xiě)了finalize方法有關(guān),不在本文分析范圍之內(nèi),我們接下來(lái)看看Phantom references。
public?class?PhantomReference<T>?extends?Reference<T>?{ ? ????public?T?get()?{ ????????return?null; ????} ? ????public?PhantomReference(T?referent,?ReferenceQueue<??super?T>?q)?{ ????????super(referent,?q); ????} }
可以看到虛引用的get方法永遠(yuǎn)返回null,我們看個(gè)demo。
public?static?void?demo()?throws?InterruptedException?{ ????????Object?obj?=?new?Object(); ????????ReferenceQueue<Object>?refQueue?=new?ReferenceQueue<>(); ????????PhantomReference<Object>?phanRef?=new?PhantomReference<>(obj,?refQueue); ????????Object?objg?=?phanRef.get(); ????????//這里拿到的是null ????????System.out.println(objg); ????????//讓obj變成垃圾 ????????obj=null; ????????System.gc(); ????????Thread.sleep(3000); //gc后會(huì)將phanRef加入到refQueue中 ????????Reference<??extends?Object>?phanRefP?=?refQueue.remove(); ????? //這里輸出true ????????System.out.println(phanRefP==phanRef); ????}
從以上代碼中可以看到,虛引用能夠在指向?qū)ο蟛豢蛇_(dá)時(shí)得到一個(gè)'通知'(其實(shí)所有繼承References的類都有這個(gè)功能),需要注意的是GC完成后,phanRef.referent依然指向之前創(chuàng)建Object,也就是說(shuō)Object對(duì)象一直沒(méi)被回收!
而造成這一現(xiàn)象的原因在上一小節(jié)末尾已經(jīng)說(shuō)了:對(duì)于Final references和 Phantom references,
clear_referent字段傳入的時(shí)false,也就意味著被這兩種引用類型引用的對(duì)象,如果沒(méi)有其他額外處理,在GC中是不會(huì)被回收的。
對(duì)于虛引用來(lái)說(shuō),從refQueue.remove();
得到引用對(duì)象后,可以調(diào)用clear
方法強(qiáng)行解除引用和對(duì)象之間的關(guān)系,使得對(duì)象下次可以GC時(shí)可以被回收掉。
針對(duì)文章開(kāi)頭提出的幾個(gè)問(wèn)題,看完分析,我們已經(jīng)能給出回答:
1.我們經(jīng)常在網(wǎng)上看到軟引用的介紹是:在內(nèi)存不足的時(shí)候才會(huì)回收,那內(nèi)存不足是怎么定義的?為什么才叫內(nèi)存不足?
軟引用會(huì)在內(nèi)存不足時(shí)被回收,內(nèi)存不足的定義和該引用對(duì)象get的時(shí)間以及當(dāng)前堆可用內(nèi)存大小都有關(guān)系,計(jì)算公式在上文中也已經(jīng)給出。
2.網(wǎng)上對(duì)于虛引用的介紹是:形同虛設(shè),與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期。主要用來(lái)跟蹤對(duì)象被垃圾回收器回收的活動(dòng)。真的是這樣嗎?
嚴(yán)格的說(shuō),虛引用是會(huì)影響對(duì)象生命周期的,如果不做任何處理,只要虛引用不被回收,那其引用的對(duì)象永遠(yuǎn)不會(huì)被回收。所以一般來(lái)說(shuō),從ReferenceQueue中獲得PhantomReference對(duì)象后,如果PhantomReference對(duì)象不會(huì)被回收的話(比如被其他GC ROOT可達(dá)的對(duì)象引用),需要調(diào)用clear
方法解除PhantomReference和其引用對(duì)象的引用關(guān)系。
3.虛引用在Jdk中有哪些場(chǎng)景下用到了呢?
DirectByteBuffer中是用虛引用的子類Cleaner.java
來(lái)實(shí)現(xiàn)堆外內(nèi)存回收的,后續(xù)會(huì)寫(xiě)篇文章來(lái)說(shuō)說(shuō)堆外內(nèi)存的里里外外。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。