<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>

          【98期】面試官:給我說(shuō)說(shuō)你對(duì)Java GC機(jī)制的理解?

          共 5613字,需瀏覽 12分鐘

           ·

          2020-12-07 06:04

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


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

          來(lái)自:windblog.cn/java/2019/03/27/java-gc-learning/

          寫在前面

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

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

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

          1、方法區(qū)

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

          2、堆( Heap)

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

          3、棧( Stack)

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

          4、PC寄存器

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

          5、本地方法棧

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

          初識(shí)GC

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

          Marking

          首先,所有堆中的對(duì)象都會(huì)被掃描一遍:我們總得知道哪些是垃圾,哪些是有用的物品吧。因?yàn)槔鴮?shí)在太多了,所以,你媽會(huì)把所有的要扔掉的東西都找出來(lái)并打上一個(gè)標(biāo)簽,到了時(shí)機(jī)成熟時(shí)回頭來(lái)一起處理,這樣她就能處理你不需要的廢物、舊家具,而不是把你喜歡的衣服或者身份證之類的東西扔掉。

          Normal Deletion

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

          Deletion with Compacting

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

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

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

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

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

          分代與GC機(jī)制

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

          新生代

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

          首先標(biāo)記不可達(dá)對(duì)象。

          然后移動(dòng)存活的對(duì)象到 to區(qū),并保證他們?cè)趦?nèi)存中連續(xù)。

          清掃垃圾。

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

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

          老年代

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

          在清除前,黃色的為不可達(dá)對(duì)象。

          在清除后,全部都變成可達(dá)對(duì)象。

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

          永久代

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

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

          一般而言, GC不應(yīng)該成為影響系統(tǒng)性能的瓶頸,我們?cè)谠u(píng)估 GC收集器的優(yōu)劣時(shí)一般考慮以下幾點(diǎn):
          • 吞吐量

          • GC開(kāi)銷

          • 暫停時(shí)間

          • GC頻率

          • 堆空間

          • 對(duì)象生命周期

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

          Serial

          Serial收集器使用了標(biāo)記-復(fù)制的算法,可以用 -XX:+UseSerialGC使用單線程的串行收集器。但是在 GC進(jìn)行時(shí),程序會(huì)進(jìn)入長(zhǎng)時(shí)間的暫停時(shí)間,一般不太建議使用。

          Parallel

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

          CMS ( ConcurrentMarkSweep)

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

          G1( GarbageFirst)

          -XX:+UseG1GC 在堆比較大的時(shí)候,如果 full gc頻繁,會(huì)導(dǎo)致停頓,并且調(diào)用方阻塞、超時(shí)、甚至雪崩的情況出現(xiàn),所以降低 full gc的發(fā)生頻率和需要時(shí)間,非常有必要。G1的誕生正是為了降低 FullGC的次數(shù),而相較于 CMS, G1使用了標(biāo)記-壓縮清除算法,這可以大大降低較大內(nèi)存( 4GB以上) GC時(shí)產(chǎn)生的內(nèi)存碎片。
          G1提供了兩種 GC模式, YoungGC和 MixedGC,兩種都是 StopTheWorld(STW)的。YoungGC主要是對(duì) Eden區(qū)進(jìn)行 GC, MixGC不僅進(jìn)行正常的新生代垃圾收集,同時(shí)也回收部分后臺(tái)掃描線程標(biāo)記的老年代分區(qū)。
          另外有趣的一點(diǎn), G1將新生代、老年代的物理空間劃分取消了,而是將堆劃分為若干個(gè)區(qū)域( region),每個(gè)大小都為 2的倍數(shù)且大小全部一致,最多有 2000個(gè)。除此之外, G1專門劃分了一個(gè) Humongous區(qū),它用來(lái)專門存放超過(guò)一個(gè) region 50%大小的巨型對(duì)象。在正常的處理過(guò)程中,對(duì)象從一個(gè)區(qū)域復(fù)制到另外一個(gè)區(qū)域,同時(shí)也完成了堆的壓縮。
          常用參數(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:?jiǎn)⒂?span style="max-width: 100%;font-size: inherit;color: rgb(204, 120, 50);line-height: inherit;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">G1垃圾回收器

          推薦閱讀:

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

          【96期】盤點(diǎn)那些關(guān)于Nginx的常考面試題

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

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

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

          朕已閱?

          瀏覽 49
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  中国久久精品 | 日本特级黄色电影免费看 | 爱搞搞视频网站 | 综合大香蕉 | 天天看片天天操 |