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>

        【98期】面試官:給我說說你對Java GC機制的理解?

        共 5613字,需瀏覽 12分鐘

         ·

        2020-12-07 06:04

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


        閱讀本文大概需要 5.5 分鐘。

        來自:windblog.cn/java/2019/03/27/java-gc-learning/

        寫在前面

        使用Java快一年時間了,從最早大學時候?qū)ava的憎惡,到逐漸接受,到工作中體會到了Java開發(fā)的各種便捷與福利,這確實是一門不錯的開發(fā)語言。不僅是 Intellij開發(fā)Java程序的爽快,還有無需手動管理內(nèi)存的便捷、 Maven管理依賴的整潔、 SpringCloud大禮包的規(guī)整等等。
        所以,作為一個有追求的Java程序員,深入底層掌握 GC(垃圾回收)的機制,應該算是必備的技能了。本文即我在學習過程中的一些個人觀點以及心得,不正之處敬請指正。

        JVM的運行數(shù)據(jù)區(qū)

        首先我簡單來畫一張 JVM的結(jié)構(gòu)原理圖,如下。
        我們重點關(guān)注 JVM在運行時的數(shù)據(jù)區(qū),你可以看到在程序運行時,大致有5個部分。

        1、方法區(qū)

        不止是存“方法”,而是存儲整個 class文件的信息,JVM運行時,類加載器子系統(tǒng)將會提取 class文件里面的類信息,并將其存放在方法區(qū)中。例如類的名稱、類的類型(枚舉、類、接口)、字段、方法等等。

        2、堆( Heap)

        熟悉 c/c++編程的同學們應該相當熟悉 Heap了,而對于Java而言,每個應用都唯一對應一個JVM實例,而每一個JVM實例唯一對應一個堆。堆主要包括關(guān)鍵字 new的對象實例、 this指針,或者數(shù)組都放在堆中,并由應用所有的線程共享。堆由JVM的自動內(nèi)存管理機制所管理,名為垃圾回收—— GC(garbage collection)。

        3、棧( Stack)

        操作系統(tǒng)內(nèi)核為某個進程或者線程建立的存儲區(qū)域,它保存著一個線程中的方法的調(diào)用狀態(tài),它具有先進后出的特性。在棧中的數(shù)據(jù)大小與生命周期嚴格來說都是確定的,例如在一個函數(shù)中聲明的int變量便是存儲在 stack中,它的大小是固定的,在函數(shù)退出后它的生命周期也從此結(jié)束。在棧中,每一個方法對應一個棧幀,JVM會對Java棧執(zhí)行兩種操作:壓棧和出棧。這兩種操作在執(zhí)行時都是以棧幀為單位的。還有一些即時編譯器編譯后的代碼等數(shù)據(jù)。

        4、PC寄存器

        pc寄存器用于存放一條指令的地址,每一個線程都有一個PC寄存器。

        5、本地方法棧

        用來調(diào)用其他語言的本地方法,例如 C/C++寫的本地代碼, 這些方法在本地方法棧中執(zhí)行,而不會在Java棧中執(zhí)行。

        初識GC

        自動垃圾回收機制,簡單來說就是尋找 Java堆中的無用對象。打個比方:你的房間是JVM的內(nèi)存,你在房間里生活會制造垃圾和臟亂,而你媽就是 GC(聽起來有點像罵人)。你媽每時每刻都覺得你房間很臟亂,不時要把你趕出門打掃房間,如果你媽一直在房間打掃,那么這個過程你無法繼續(xù)在房間打游戲吃泡面。但如果你一直在房間,你的房間早晚要變成一個無法居住的豬窩。
        那么,怎么樣回收垃圾比較好呢?我們大致可以想出下面的思路。

        Marking

        首先,所有堆中的對象都會被掃描一遍:我們總得知道哪些是垃圾,哪些是有用的物品吧。因為垃圾實在太多了,所以,你媽會把所有的要扔掉的東西都找出來并打上一個標簽,到了時機成熟時回頭來一起處理,這樣她就能處理你不需要的廢物、舊家具,而不是把你喜歡的衣服或者身份證之類的東西扔掉。

        Normal Deletion

        垃圾收集器將清除掉標記的對象:你媽已經(jīng)整理了一部分雜物(或者已全部整理完),然后會將他們直接拎出去倒掉。你很開心房間又可以繼續(xù)接受蹂躪了。

        Deletion with Compacting

        壓縮清除的方法:我們知道,內(nèi)存有空閑,并不代表著我們就能使用它,例如我們要分配數(shù)組這種一段連續(xù)空間,假如內(nèi)存中碎片較多,肯定是行不通的。正如房間可能需要再放一個新的床,但是扔掉舊衣柜后,原來的位置并不能放得下新床,所以需要進行空間壓縮,把剩下的家具和物品位置并到一起,這樣就能騰出更多的空間啦。
        有趣的是,JVM并不是使用類似于 objective-c的 ARC(AutomaticReferenceCounting)的方式來引用計數(shù)對象,而是使用了叫根搜索算法( GC Root)的方法,基本思想就是選定一些對象作為 GC Roots,并組成根對象集合,然后從這些作為 GC Roots的對象作為起始點,搜索所走過的引用鏈( ReferenceChain)。如果目標對象到 GC Roots是連接著的,我們則稱該目標對象是可達的,如果目標對象不可達,則說明目標對象是可以被回收的對象。
        GC Root使用的算法是相當復雜的,你不必記住里面的所有細節(jié)。但是你要知道的一點就是,可以作為 GC Root的對象可以主要分為四種。
        • JVM棧中引用的對象;

        • 方法區(qū)中,靜態(tài)屬性引用的對象;

        • 方法區(qū)中,常量引用的對象;

        • 本地方法棧中,JNI(即Native方法)引用的對象;

        在 JDK1.2之后,Java將引用分為強引用、軟引用、弱引用、虛引用4種,這4種引用強度依次減弱。

        分代與GC機制

        嗯,聽起來這樣就可以了?但是實際情況下,很不幸,在JVM中絕大部分對象都是英年早逝的,在編碼時大部分堆中的內(nèi)存都是短暫臨時分配的,所以無論是效率還是開銷方面,按上面那樣進行 GC往往是無法滿足我們需求的。而且,實際上隨著分配的對象增多, GC的時間與開銷將會放大。所以,JVM的內(nèi)存被分為了三個主要部分:新生代,老年代和永久代。

        新生代

        所有新產(chǎn)生的對象全部都在新生代中, Eden區(qū)保存最新的對象,有兩個 SurvivorSpace—— S1和 S0,三個區(qū)域的比例大致為 8:1:1。當新生代的 Eden區(qū)滿了,將觸發(fā)一次 GC,我們把新生代中的 GC稱為 minor garbage collections。minor garbage collections是一種 Stopthe world事件,比如你媽在打掃時,會把你趕出去,而不是你一邊扔垃圾她一邊打掃。
        我們來看下對象在堆中的分配過程,首先有新的對象進入時,默認放入新生代的 Eden區(qū), S區(qū)都是默認為空的。下面對象的數(shù)字代表經(jīng)歷了多少次 GC,也就是對象的年齡。
        當 eden區(qū)滿了,觸發(fā) minor garbage collections,這時還有被引用的對象,就會被分配到 S0區(qū)域,剩下沒有被引用的對象就都會被清除。
        再一次 GC時, S0區(qū)的部分對象很可能會出現(xiàn)沒有引用的,被引用的對象以及 S0中的存活對象,會被一起移動到 S1中。eden和 S0中的未引用對象會被全部清除。
        接下來就是無限循環(huán)上面的步驟了,當新生代中存活的對象超過了一定的【年齡】,會被分配至老年代的 Tenured區(qū)中。這個年齡可以通過參數(shù) MaxTenuringThreshold設(shè)定,默認值為 15,圖中的例子為 8次。
        新生代管理內(nèi)存采用的算法為 GC復制算法( CopyingGC),也叫標記-復制法,原理是把內(nèi)存分為兩個空間:一個 From空間,一個 To空間,對象一開始只在 From空間分配, To空間是空閑的。GC時把存活的對象從 From空間復制粘貼到 To空間,之后把 To空間變成新的 From空間,原來的 From空間變成 To空間。

        首先標記不可達對象。

        然后移動存活的對象到 to區(qū),并保證他們在內(nèi)存中連續(xù)。

        清掃垃圾。

        可以看到上圖操作后內(nèi)存幾乎都是連續(xù)的,所以它的效率是非常高的,但是相對的吞吐量會較大。并且,把內(nèi)存一分為二,占用了將近一半的可用內(nèi)存。用一段偽代碼來實現(xiàn)大致為下。
        void?copying(){
        ????????$free?=?$to_start?//?$free表示To區(qū)占用偏移量,每復制成功一個對象obj,?
        ??????????????????????????//?$free向前移動size(obj)
        ????????for(r?:?$roots)
        ????????????*r?=?copy(*r)?//?復制成功后返回新的引用

        ????????swap($from_start,?$to_start)?//?GC完成后交互From區(qū)與To區(qū)的指針
        ?}

        老年代

        老年代用來存儲活時間較長的對象,老年代區(qū)域的 GC是 major garbage collection,老年代中的內(nèi)存不夠時,就會觸發(fā)一次。這也是一個 Stopthe world事件,但是看名字就知道,這個回收過程會相當慢,因為這包括了對新生代和老年代所有對象的回收,也叫 FullGC。
        老年代管理內(nèi)存最早采用的算法為標記-清理算法,這個算法很好理解,結(jié)合 GC Root的定義,我們會把所有不可達的對象全部標記進行清除。

        在清除前,黃色的為不可達對象。

        在清除后,全部都變成可達對象。

        那么,這個算法的劣勢很好理解:對,會在標記清除的過程中產(chǎn)生大量的內(nèi)存碎片,Java在分配內(nèi)存時通常是按連續(xù)內(nèi)存分配,這樣我們會浪費很多內(nèi)存。所以,現(xiàn)在的 JVM GC在老年代都是使用標記-壓縮清除方法,將上圖在清除后的內(nèi)存進行整理和壓縮,以保證內(nèi)存連續(xù),雖然這個算法的效率是三種算法里最低的。

        永久代

        永久代位于方法區(qū),主要存放元數(shù)據(jù),例如 Class、 Method的元信息,與 GC要回收的對象其實關(guān)系并不是很大,我們可以幾乎忽略其對 GC的影響。除了 JavaHotSpot這種較新的虛擬機技術(shù),會回收無用的常量和的類,以免大量運用反射這類頻繁自定義 ClassLoader的操作時方法區(qū)溢出。

        GC收集器與優(yōu)化

        一般而言, GC不應該成為影響系統(tǒng)性能的瓶頸,我們在評估 GC收集器的優(yōu)劣時一般考慮以下幾點:
        • 吞吐量

        • GC開銷

        • 暫停時間

        • GC頻率

        • 堆空間

        • 對象生命周期

        所以針對不同的 GC收集器,我們要對應我們的應用場景來進行選擇和調(diào)優(yōu),回顧 GC的歷史,主要有 4種 GC收集器: Serial、 Parallel、 CMS和 G1。

        Serial

        Serial收集器使用了標記-復制的算法,可以用 -XX:+UseSerialGC使用單線程的串行收集器。但是在 GC進行時,程序會進入長時間的暫停時間,一般不太建議使用。

        Parallel

        -XX:+UseParallelGC-XX:+UseParallelOldGCParallel也使用了標記-復制的算法,但是我們稱之為吞吐量優(yōu)先的收集器,因為 Parallel最主要的優(yōu)勢在于并行使用多線程去完成垃圾清理工作,這樣可以充分利用多核的特性,大幅降低 gc時間。當你的程序場景吞吐量較大,例如消息隊列這種應用,需要保證有效利用 CPU資源,可以忍受一定的停頓時間,可以優(yōu)先考慮這種方式。

        CMS ( ConcurrentMarkSweep)

        -XX:+UseParNewGC-XX:+UseConcMarkSweepGCCMS使用了標記-清除的算法,當應用尤其重視服務(wù)器的響應速度(比如 Apiserver),希望系統(tǒng)停頓時間最短,以給用戶帶來較好的體驗,那么可以選擇 CMS。CMS收集器在 MinorGC時會暫停所有的應用線程,并以多線程的方式進行垃圾回收。在 FullGC時不暫停應用線程,而是使用若干個后臺線程定期的對老年代空間進行掃描,及時回收其中不再使用的對象。

        G1( GarbageFirst)

        -XX:+UseG1GC 在堆比較大的時候,如果 full gc頻繁,會導致停頓,并且調(diào)用方阻塞、超時、甚至雪崩的情況出現(xiàn),所以降低 full gc的發(fā)生頻率和需要時間,非常有必要。G1的誕生正是為了降低 FullGC的次數(shù),而相較于 CMS, G1使用了標記-壓縮清除算法,這可以大大降低較大內(nèi)存( 4GB以上) GC時產(chǎn)生的內(nèi)存碎片。
        G1提供了兩種 GC模式, YoungGC和 MixedGC,兩種都是 StopTheWorld(STW)的。YoungGC主要是對 Eden區(qū)進行 GC, MixGC不僅進行正常的新生代垃圾收集,同時也回收部分后臺掃描線程標記的老年代分區(qū)。
        另外有趣的一點, G1將新生代、老年代的物理空間劃分取消了,而是將堆劃分為若干個區(qū)域( region),每個大小都為 2的倍數(shù)且大小全部一致,最多有 2000個。除此之外, G1專門劃分了一個 Humongous區(qū),它用來專門存放超過一個 region 50%大小的巨型對象。在正常的處理過程中,對象從一個區(qū)域復制到另外一個區(qū)域,同時也完成了堆的壓縮。
        常用參數(shù)
        -XX:+UseSerialGC:在新生代和老年代使用串行收集器
        -XX:+UseParNewGC:在新生代使用并行收集器
        -XX:+UseParallelGC?:新生代使用并行回收收集器,更加關(guān)注吞吐量
        -XX:+UseParallelOldGC:老年代使用并行回收收集器
        -XX:ParallelGCThreads:設(shè)置用于垃圾回收的線程數(shù)
        -XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
        -XX:ParallelCMSThreads:設(shè)定CMS的線程數(shù)量
        -XX:+UseG1GC:啟用G1垃圾回收器

        推薦閱讀:

        【97期】一網(wǎng)打盡面試中常被問及的8種數(shù)據(jù)結(jié)構(gòu)

        【96期】盤點那些關(guān)于Nginx的??济嬖囶}

        【95期】面試官:你遇到 Redis 線上連接超時一般如何處理?

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

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

        朕已閱?

        瀏覽 49
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        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>
            亚洲天堂AV网 | 强壮公次次弄得我高潮a日剧 | 国产黄片乱伦 | 99re国产 | 日韩一级在线免费观看 | 豆花网站免费观看 | 国产黄色美女视频 | 男人裸体写真集露大乌 | 青娱乐精品| 欧洲性爱无码 |