1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【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永遠返回false,代表永遠不回收SoftReference,在JVM中該類沒有被使用,AlwaysClearPolicy則永遠返回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時堆使用大?。┯嘘P(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ù)期:對象不可達后,引用字段就會被置為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方法永遠返回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ū)ο蟛豢蛇_時得到一個'通知'(其實所有繼承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)格的說,虛引用是會影響對象生命周期的,如果不做任何處理,只要虛引用不被回收,那其引用的對象永遠不會被回收。所以一般來說,從ReferenceQueue中獲得PhantomReference對象后,如果PhantomReference對象不會被回收的話(比如被其他GC ROOT可達的對象引用),需要調(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
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美毛片少妇蜜桃 | 黄片免费大全 | 丰满熟妇毛茸茸HD | 少妇口述老外性厉害 | 免费网站观看www在线观看 | 69操逼网站| 一级视频在线观看免费 | 老司机毛片| 午夜福利爱爱 | 免费成人黄色片 |