<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【55期】面試中經(jīng)常被問到Java引用類型原理,帶你深入剖析

          共 714字,需瀏覽 2分鐘

           ·

          2020-10-11 20:15

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
          關(guān)注


          閱讀本文大概需要 8.5?分鐘。

          來自:github.com/farmerjohngit/myblog/issues/10

          Java中一共有4種引用類型(其實還有一些其他的引用類型比如FinalReference):強引用、軟引用、弱引用、虛引用。
          其中強引用就是我們經(jīng)常使用的Object a = new Object(); 這樣的形式,在Java中并沒有對應(yīng)的Reference類。
          本篇文章主要是分析軟引用、弱引用、虛引用的實現(xiàn),這三種引用類型都是繼承于Reference這個類,主要邏輯也在Reference中。

          問題

          在分析前,先拋幾個問題?
          1.網(wǎng)上大多數(shù)文章對于軟引用的介紹是:在內(nèi)存不足的時候才會被回收,那內(nèi)存不足是怎么定義的?什么才叫內(nèi)存不足?
          2.網(wǎng)上大多數(shù)文章對于虛引用的介紹是:形同虛設(shè),虛引用并不會決定對象的生命周期。主要用來跟蹤對象被垃圾回收器回收的活動。真的是這樣嗎?
          3.虛引用在Jdk中有哪些場景下用到了呢?

          Reference

          我們先看下Reference.java中的幾個字段
          public?abstract?class?Reference<T>?{
          ????//引用的對象
          ????private?T?referent;????????
          ????//回收隊列,由使用者在Reference的構(gòu)造函數(shù)中指定
          ????volatile?ReferenceQueuesuper?T>?queue;
          ?????//當(dāng)該引用被加入到queue中的時候,該字段被設(shè)置為queue中的下一個元素,以形成鏈表結(jié)構(gòu)
          ????volatile?Reference?next;
          ????//在GC時,JVM底層會維護一個叫DiscoveredList的鏈表,存放的是Reference對象,discovered字段指向的就是鏈表中的下一個元素,由JVM設(shè)置
          ????transient?private?Reference?discovered;??
          ????//進行線程同步的鎖對象
          ????static?private?class?Lock?{?}
          ????private?static?Lock?lock?=?new?Lock();
          ????//等待加入queue的Reference對象,在GC時由JVM設(shè)置,會有一個java層的線程(ReferenceHandler)源源不斷的從pending中提取元素加入到queue
          ????private?static?Reference?pending?=?null;
          }
          一個Reference對象的生命周期如下:
          主要分為Native層和Java層兩個部分。
          Native層在GC時將需要被回收的Reference對象加入到DiscoveredList中(代碼在referenceProcessor.cpp中process_discovered_references方法),然后將DiscoveredList的元素移動到PendingList中(代碼在referenceProcessor.cpp中enqueue_discovered_ref_helper方法),PendingList的隊首就是Reference類中的pending對象。
          看看Java層的代碼
          private?static?class?ReferenceHandler?extends?Thread?{
          ?????????...
          ????????public?void?run()?{
          ????????????while?(true)?{
          ????????????????tryHandlePending(true);
          ????????????}
          ????????}
          ??}?
          static?boolean?tryHandlePending(boolean?waitForNotify)?{
          ????????Reference?r;
          ????????Cleaner?c;
          ????????try?{
          ????????????synchronized?(lock)?{
          ????????????????if?(pending?!=?null)?{
          ????????????????????r?=?pending;
          ?????????????????????//如果是Cleaner對象,則記錄下來,下面做特殊處理
          ????????????????????c?=?r?instanceof?Cleaner???(Cleaner)?r?:?null;
          ????????????????????//指向PendingList的下一個對象
          ????????????????????pending?=?r.discovered;
          ????????????????????r.discovered?=?null;
          ????????????????}?else?{
          ???????????????????//如果pending為null就先等待,當(dāng)有對象加入到PendingList中時,jvm會執(zhí)行notify
          ????????????????????if?(waitForNotify)?{
          ????????????????????????lock.wait();
          ????????????????????}
          ????????????????????//?retry?if?waited
          ????????????????????return?waitForNotify;
          ????????????????}
          ????????????}
          ????????}?
          ????????...

          ????????//?如果時CLeaner對象,則調(diào)用clean方法進行資源回收
          ????????if?(c?!=?null)?{
          ????????????c.clean();
          ????????????return?true;
          ????????}
          ????????//將Reference加入到ReferenceQueue,開發(fā)者可以通過從ReferenceQueue中poll元素感知到對象被回收的事件。
          ????????ReferenceQueuesuper?Object>?q?=?r.queue;
          ????????if?(q?!=?ReferenceQueue.NULL)?q.enqueue(r);
          ????????return?true;
          ?}
          流程比較簡單:就是源源不斷的從PendingList中提取出元素,然后將其加入到ReferenceQueue中去,開發(fā)者可以通過從ReferenceQueue中poll元素感知到對象被回收的事件。
          另外需要注意的是,對于Cleaner類型(繼承自虛引用)的對象會有額外的處理:在其指向的對象被回收時,會調(diào)用clean方法,該方法主要是用來做對應(yīng)的資源回收,在堆外內(nèi)存DirectByteBuffer中就是用Cleaner進行堆外內(nèi)存的回收,這也是虛引用在java中的典型應(yīng)用。
          看完了Reference的實現(xiàn),再看看幾個實現(xiàn)類里,各自有什么不同。
          SoftReference
          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,?ReferenceQueuesuper?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;
          ????}

          }
          軟引用的實現(xiàn)很簡單,就多了兩個字段:clock和timestamp。clock是個靜態(tài)變量,每次GC時都會將該字段設(shè)置成當(dāng)前時間。timestamp字段則會在每次調(diào)用get方法時將其賦值為clock(如果不相等且對象沒被回收)。
          那這兩個字段的作用是什么呢?這和軟引用在內(nèi)存不夠的時候才被回收,又有什么關(guān)系呢?
          這些還得看JVM的源碼才行,因為決定對象是否需要被回收都是在GC中實現(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)
          {
          ?...
          ???//還記得上文提到過的DiscoveredList嗎?refs_lists就是DiscoveredList。
          ???//對于DiscoveredList的處理分為幾個階段,SoftReference的處理就在第一階段
          ?...
          ??????for?(uint?i?=?0;?i?????????process_phase1(refs_lists[i],?policy,
          ???????????????????????is_alive,?keep_alive,?complete_gc);
          ??????}
          ?...
          }

          //該階段的主要目的就是當(dāng)內(nèi)存足夠時,將對應(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?*/));
          ????//判斷引用的對象是否存活
          ????bool?referent_is_dead?=?(iter.referent()?!=?NULL)?&&?!iter.is_referent_alive();
          ????//如果引用的對象已經(jīng)不存活了,則會去調(diào)用對應(yīng)的ReferencePolicy判斷該對象是不時要被回收
          ????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方法的作用就是將不需要被回收的對象從refs_lists移除掉,refs_lists最后剩下的元素全是需要被回收的元素,最后會將其第一個元素賦值給上文提到過的Reference.java#pending字段。
          ReferencePolicy一共有4種實現(xiàn):NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。
          其中NeverClearPolicy永遠(yuǎn)返回false,代表永遠(yuǎn)不回收SoftReference,在JVM中該類沒有被使用,AlwaysClearPolicy則永遠(yuǎn)返回true,在referenceProcessor.hpp#setup方法中中可以設(shè)置policy為AlwaysClearPolicy,至于什么時候會用到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)對應(yīng)是字段timestamp。如果上次GC后有調(diào)用SoftReference#get,interval值為0,否則為若干次GC之間的時間差。
          _max_interval則代表了一個臨界值,它的值在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,前者的計算方法和上次GC后可用堆大小有關(guān),后者計算方法和(堆大小-上次gc時堆使用大小)有關(guān)。
          看到這里你就知道SoftReference到底什么時候被被回收了,它和使用的策略(默認(rèn)應(yīng)該是LRUCurrentHeapPolicy),堆可用大小,該SoftReference上一次調(diào)用get方法的時間都有關(guān)系。

          WeakReference

          public?class?WeakReference<T>?extends?Reference<T>?{

          ????public?WeakReference(T?referent)?{
          ????????super(referent);
          ????}

          ????public?WeakReference(T?referent,?ReferenceQueuesuper?T>?q)?{
          ????????super(referent,?q);
          ????}

          }
          可以看到WeakReference在Java層只是繼承了Reference,沒有做任何的改動。那referent字段是什么時候被置為null的呢?要搞清楚這個問題我們再看下上文提到過的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為軟引用的時候,這里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?????????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???????process_phase2(refs_lists[i],?is_alive,?keep_alive,?complete_gc);
          ????}
          ??}

          ??//?Phase?3:
          ??//?根據(jù)clear_referent的值決定是否將不存活對象回收
          ??if?(mt_processing)?{
          ????RefProcPhase3Task?phase3(*this,?refs_lists,?clear_referent,?true?/*marks_oops_alive*/);
          ????task_executor->execute(phase3);
          ??}?else?{
          ????for?(uint?i?=?0;?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,之后會被GC回收
          ??????iter.clear_referent();
          ????}?else?{
          ??????//?keep?the?referent?around
          ??????//標(biāo)記引用的對象為存活,該對象在這次GC將不會被回收
          ??????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個字段就是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);
          ??}
          ????...
          }
          可以看到,對于Soft references和Weak references clear_referent字段傳入的都是true,這也符合我們的預(yù)期:對象不可達(dá)后,引用字段就會被置為null,然后對象就會被回收(對于軟引用來說,如果內(nèi)存足夠的話,在Phase 1,相關(guān)的引用就會從refs_list中被移除,到Phase 3時refs_list為空集合)。
          但對于Final references和 Phantom references,clear_referent字段傳入的是false,也就意味著被這兩種引用類型引用的對象,如果沒有其他額外處理,只要Reference對象還存活,那引用的對象是不會被回收的。Final references和對象是否重寫了finalize方法有關(guān),不在本文分析范圍之內(nèi),我們接下來看看Phantom references。

          PhantomReference

          public?class?PhantomReference<T>?extends?Reference<T>?{

          ????public?T?get()?{
          ????????return?null;
          ????}

          ????public?PhantomReference(T?referent,?ReferenceQueuesuper?T>?q)?{
          ????????super(referent,?q);
          ????}

          }
          可以看到虛引用的get方法永遠(yuǎn)返回null,我們看個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后會將phanRef加入到refQueue中
          ????????Referenceextends?Object>?phanRefP?=?refQueue.remove();
          ?????????//這里輸出true
          ????????System.out.println(phanRefP==phanRef);
          ????}
          從以上代碼中可以看到,虛引用能夠在指向?qū)ο蟛豢蛇_(dá)時得到一個'通知'(其實所有繼承References的類都有這個功能),需要注意的是GC完成后,phanRef.referent依然指向之前創(chuàng)建Object,也就是說Object對象一直沒被回收!
          而造成這一現(xiàn)象的原因在上一小節(jié)末尾已經(jīng)說了:對于Final references和 Phantom references,clear_referent字段傳入的時false,也就意味著被這兩種引用類型引用的對象,如果沒有其他額外處理,在GC中是不會被回收的。
          對于虛引用來說,從refQueue.remove();得到引用對象后,可以調(diào)用clear方法強行解除引用和對象之間的關(guān)系,使得對象下次可以GC時可以被回收掉。

          End

          針對文章開頭提出的幾個問題,看完分析,我們已經(jīng)能給出回答:
          1.我們經(jīng)常在網(wǎng)上看到軟引用的介紹是:在內(nèi)存不足的時候才會回收,那內(nèi)存不足是怎么定義的?為什么才叫內(nèi)存不足?
          軟引用會在內(nèi)存不足時被回收,內(nèi)存不足的定義和該引用對象get的時間以及當(dāng)前堆可用內(nèi)存大小都有關(guān)系,計算公式在上文中也已經(jīng)給出。
          2.網(wǎng)上對于虛引用的介紹是:形同虛設(shè),與其他幾種引用都不同,虛引用并不會決定對象的生命周期。主要用來跟蹤對象被垃圾回收器回收的活動。真的是這樣嗎?
          嚴(yán)格的說,虛引用是會影響對象生命周期的,如果不做任何處理,只要虛引用不被回收,那其引用的對象永遠(yuǎn)不會被回收。所以一般來說,從ReferenceQueue中獲得PhantomReference對象后,如果PhantomReference對象不會被回收的話(比如被其他GC ROOT可達(dá)的對象引用),需要調(diào)用clear方法解除PhantomReference和其引用對象的引用關(guān)系。
          3.虛引用在Jdk中有哪些場景下用到了呢?
          DirectByteBuffer中是用虛引用的子類Cleaner.java來實現(xiàn)堆外內(nèi)存回收的,后續(xù)會寫篇文章來說說堆外內(nèi)存的里里外外。

          推薦閱讀:

          【54期】Java序列化三連問,是什么?為什么需要?如何實現(xiàn)?

          【53期】面試官:談一下數(shù)據(jù)庫分庫分表之后,你是如何解決事務(wù)問題?

          【52期】記一道簡單的Java面試題,但答錯率很高!

          5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機,樹莓派,等等。在公眾號內(nèi)回復(fù)「2048」,即可免費獲取!!

          微信掃描二維碼,關(guān)注我的公眾號

          朕已閱?

          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    黄片大全在线看 | 人妻无码视频免费看 | 8809鲁大师日韩版免费使用 | 青娱乐在线视频2 | 日本毛片在线观看 |