日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

G1 垃圾收集器原理详解

發(fā)布時(shí)間:2024/9/30 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 G1 垃圾收集器原理详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、G1 垃圾收集器的開發(fā)背景:

1、CMS 垃圾收集器的缺陷:

? ? ? ? JVM 團(tuán)隊(duì)設(shè)計(jì)出 G1 收集器的目的就是取代 CMS 收集器,因?yàn)?CMS 收集器在很多場(chǎng)景下存在諸多問題,缺陷暴露無遺,具體如下:

(1)CMS收集器對(duì)CPU資源非常敏感。在并發(fā)階段,雖然不會(huì)導(dǎo)致用戶線程停頓,但是會(huì)占用CPU資源而導(dǎo)致引用程序變慢,總吞吐量下降。CMS默認(rèn)啟動(dòng)的回收線程數(shù)是:(CPU數(shù)量+3) / 4

(2)CMS收集器無法處理浮動(dòng)垃圾,由于CMS并發(fā)清理階段用戶線程還在運(yùn)行,伴隨程序的運(yùn)行自然會(huì)有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過程之后,稱為“浮動(dòng)垃圾”,CMS 無法在本次收集中處理它們,只好留待下一次GC時(shí)將其清理掉。

(3)由于垃圾收集階段會(huì)產(chǎn)生“浮動(dòng)垃圾”,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進(jìn)行收集,需要預(yù)留一部分內(nèi)存空間提供并發(fā)收集時(shí)的程序運(yùn)作使用。在默認(rèn)設(shè)置下,CMS收集器在老年代使用了68%的空間時(shí)就會(huì)被激活,也可以通過參數(shù)-XX:CMSInitiatingOccupancyFraction的值來提高觸發(fā)百分比,以降低內(nèi)存回收次數(shù)提高性能。要是CMS運(yùn)行期間預(yù)留的內(nèi)存無法滿足程序其他線程需要,就會(huì)出現(xiàn)“Concurrent Mode Failure”失敗,這時(shí)候虛擬機(jī)將啟動(dòng)后備預(yù)案:臨時(shí)啟用Serial Old收集器來重新進(jìn)行老年代的垃圾收集,這樣停頓時(shí)間就很長(zhǎng)了。所以參數(shù) -XX:CMSInitiatingOccupancyFraction 設(shè)置的過高將會(huì)很容易導(dǎo)致 “Concurrent Mode Failure” 失敗,性能反而降低。

(4)CMS是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的收集器,會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片。當(dāng)老年代空間碎片太多時(shí),如果無法找到一塊足夠大的連續(xù)內(nèi)存存放對(duì)象時(shí),將不得不提前觸發(fā)一次Full GC。為了解決這個(gè)問題,CMS收集器提供了一個(gè)-XX:UseCMSCompactAtFullCollection開關(guān)參數(shù),用于在Full ?GC之后增加一個(gè)碎片整理過程,還可通過-XX:CMSFullGCBeforeCompaction參數(shù)設(shè)置執(zhí)行多少次不壓縮的Full ?GC之后,跟著來一次碎片整理過程。

2、G1 垃圾收集器的特點(diǎn):

????????G1(Garbage First)收集器是 JDK7 提供的一個(gè)新收集器,在 JDK9 中更被指定為官方GC收集器,與CMS收集器相比,最突出的改進(jìn)是:

  • 基于 “標(biāo)記-整理” 算法,收集后不會(huì)產(chǎn)生內(nèi)存碎片。
  • 可以非常精確控制停頓時(shí)間,在不犧牲吞吐量前提下,實(shí)現(xiàn)低停頓垃圾回收。

在介紹G1的垃圾收集流程之前,我們先簡(jiǎn)單了解下G1中的內(nèi)存模型以及主要的數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)果對(duì)我們了解G1的垃圾回收流程十分重要

二、G1 垃圾收集器的內(nèi)存模型:

????????G1 收集器不采用傳統(tǒng)的新生代和老年代物理隔離的布局方式,僅在邏輯上劃分新生代和老年代,將整個(gè)堆內(nèi)存劃分為2048個(gè)大小相等的獨(dú)立內(nèi)存塊Region,每個(gè)Region是邏輯連續(xù)的一段內(nèi)存,具體大小根據(jù)堆的實(shí)際大小而定,整體被控制在 1M - 32M 之間,且為2的N次冪(1M、2M、4M、8M、16M和32M),并使用不同的Region來表示新生代和老年代,G1不再要求相同類型的 Region 在物理內(nèi)存上相鄰,而是通過Region的動(dòng)態(tài)分配方式實(shí)現(xiàn)邏輯上的連續(xù)。

????????G1收集器通過跟蹤Region中的垃圾堆積情況,每次根據(jù)設(shè)置的垃圾回收時(shí)間,回收優(yōu)先級(jí)最高的區(qū)域,避免整個(gè)新生代或整個(gè)老年代的垃圾回收,使得stop the world的時(shí)間更短、更可控,同時(shí)在有限的時(shí)間內(nèi)可以獲得最高的回收效率。

? ? ? ? 通過區(qū)域劃分和優(yōu)先級(jí)區(qū)域回收機(jī)制,確保G1收集器可以在有限時(shí)間獲得最高的垃圾收集效率。

1、分區(qū)Region:

? ? ? ? G1 垃圾收集器將堆內(nèi)存劃分為若干個(gè) Region,每個(gè)?Region 分區(qū)只能是一種角色,Eden區(qū)、S區(qū)、老年代O區(qū)的其中一個(gè),空白區(qū)域代表的是未分配的內(nèi)存,最后還有個(gè)特殊的區(qū)域H區(qū)(Humongous),專門用于存放巨型對(duì)象,如果一個(gè)對(duì)象的大小超過Region容量的50%以上,G1 就認(rèn)為這是個(gè)巨型對(duì)象。在其他垃圾收集器中,這些巨型對(duì)象默認(rèn)會(huì)被分配在老年代,但如果它是一個(gè)短期存活的巨型對(duì)象,放入老年代就會(huì)對(duì)垃圾收集器造成負(fù)面影響,觸發(fā)老年代頻繁GC。為了解決這個(gè)問題,G1劃分了一個(gè)H區(qū)專門存放巨型對(duì)象,如果一個(gè)H區(qū)裝不下巨型對(duì)象,那么G1會(huì)尋找連續(xù)的H分區(qū)來存儲(chǔ),如果尋找不到連續(xù)的H區(qū)的話,就不得不啟動(dòng) Full GC 了。

2、Remember Set:

????????在串行和并行收集器中,GC時(shí)是通過整堆掃描來確定對(duì)象是否處于可達(dá)路徑中。然而G1為了避免STW式的整堆掃描,為每個(gè)分區(qū)各自分配了一個(gè)?RSet(Remembered Set),它內(nèi)部類似于一個(gè)反向指針,記錄了其它?Region 對(duì)當(dāng)前?Region 的引用情況,這樣就帶來一個(gè)極大的好處:回收某個(gè)Region時(shí),不需要執(zhí)行全堆掃描,只需掃描它的 RSet 就可以找到外部引用,來確定引用本分區(qū)內(nèi)的對(duì)象是否存活,進(jìn)而確定本分區(qū)內(nèi)的對(duì)象存活情況,而這些引用就是 initial mark 的根之一。

????????事實(shí)上,并非所有的引用都需要記錄在RSet中,如果引用源是本分區(qū)的對(duì)象,那么就不需要記錄在 RSet 中;同時(shí) G1 每次 GC 時(shí),所有的新生代都會(huì)被掃描,因此引用源是年輕代的對(duì)象,也不需要在RSet中記錄;所以最終只需要記錄老年代到新生代之間的引用即可。

3、Card Table:

????????如果一個(gè)線程修改了Region內(nèi)部的引用,就必須要去通知RSet,更改其中的記錄。需要注意的是,如果引用的對(duì)象很多,賦值器需要對(duì)每個(gè)引用做處理,賦值器開銷會(huì)很大,因此 G1 回收器引入了?Card Table 解決這個(gè)問題。

????????一個(gè) Card Table 將一個(gè) Region 在邏輯上劃分為若干個(gè)固定大小(介于128到512字節(jié)之間)的連續(xù)區(qū)域,每個(gè)區(qū)域稱之為卡片 Card,因此 Card 是堆內(nèi)存中的最小可用粒度,分配的對(duì)象會(huì)占用物理上連續(xù)的若干個(gè)卡片,當(dāng)查找對(duì)分區(qū)內(nèi)對(duì)象的引用時(shí)便可通過卡片 Card 來查找(見RSet),每次對(duì)內(nèi)存的回收,也都是對(duì)指定分區(qū)的卡片進(jìn)行處理。每個(gè) Card 都用一個(gè) Byte 來記錄是否修改過,Card Table 就是這些 Byte 的集合,是一個(gè)字節(jié)數(shù)組,由 Card 的數(shù)組下標(biāo)來標(biāo)識(shí)每個(gè)分區(qū)的空間地址。默認(rèn)情況下,每個(gè) Card 都未被引用,當(dāng)一個(gè)地址空間被引用時(shí),這個(gè)地址空間對(duì)應(yīng)的數(shù)組索引的值被標(biāo)記為”0″,即標(biāo)記為臟被引用,此外 RSet 也將這個(gè)數(shù)組下標(biāo)記錄下來。

? ? ? ? 一個(gè)Region可能有多個(gè)線程在并發(fā)修改,因此也可能會(huì)并發(fā)修改 RSet。為避免沖突,G1垃圾回收器進(jìn)一步把 RSet 劃分成了多個(gè) HashTable,每個(gè)線程都在各自的 HashTable 里修改。最終,從邏輯上來說,RSet 就是這些 HashTable 的集合。哈希表是實(shí)現(xiàn) RSet 的一種常見方式,它的好處就是能夠去除重復(fù),這意味著,RS的大小將和修改的指針數(shù)量相當(dāng),而在不去重的情況下,RS的數(shù)量和寫操作的數(shù)量相當(dāng)。

HashTable 的 Key 是別的 Region 的起始地址,Value是一個(gè)集合,里面的元素是Card Table的Index。

前面三個(gè)數(shù)據(jù)結(jié)構(gòu)的關(guān)系如下:

????????圖中RS的虛線表明的是,RSet 并不是一個(gè)和 Card Table獨(dú)立的、不同的數(shù)據(jù)結(jié)構(gòu),而是指RS是一個(gè)概念模型。實(shí)際上,Card Table 是 RS 的一種實(shí)現(xiàn)方式。

G1對(duì)內(nèi)存的使用以分區(qū)(Region)為單位,而對(duì)對(duì)象的分配則以卡片(Card)為單位。

4、RSet 的寫屏障:

????????寫屏障是指,每次 Reference 引用類型在執(zhí)行寫操作時(shí),都會(huì)產(chǎn)生 Write Barrier?寫屏障暫時(shí)中斷操作并額外執(zhí)行一些動(dòng)作。

????????對(duì)寫屏障來說,過濾掉不必要的寫操作是十分有必要的,因?yàn)閷憱艡诘闹噶铋_銷是十分昂貴的,這樣既能加快賦值器的速度,也能減輕回收器的負(fù)擔(dān)。G1 收集器的寫屏障是跟 RSet 相輔相成的,產(chǎn)生寫屏障時(shí)會(huì)檢查要寫入的引用指向的對(duì)象是否和該 Reference 類型數(shù)據(jù)在不同的 Region,如果不同,才通過 CardTable 把相關(guān)引用信息記錄到引用指向?qū)ο蟮乃?Region 對(duì)應(yīng)的 RSet?中,通過過濾就能使 RSet?大大減少。

(1)寫前柵欄:即將執(zhí)行一段賦值語(yǔ)句時(shí),等式左側(cè)對(duì)象將修改引用到另一個(gè)對(duì)象,那么等式左側(cè)對(duì)象原先引用的對(duì)象所在分區(qū)將因此喪失一個(gè)引用,那么JVM就需要在賦值語(yǔ)句生效之前,記錄喪失引用的對(duì)象。但JVM并不會(huì)立即維護(hù)RSet,而是通過批量處理,在將來RSet更新

(2)寫后柵欄:當(dāng)執(zhí)行一段賦值語(yǔ)句后,等式右側(cè)對(duì)象獲取了左側(cè)對(duì)象的引用,那么等式右側(cè)對(duì)象所在分區(qū)的RSet也應(yīng)該得到更新。同樣為了降低開銷,寫后柵欄發(fā)生后,RSet也不會(huì)立即更新,同樣只是記錄此次更新日志,在將來批量處理

????????G1垃圾回收器進(jìn)行垃圾回收時(shí),在GC根節(jié)點(diǎn)枚舉范圍加入RSet,就可以保證不進(jìn)行全局掃描,也不會(huì)有遺漏。另外JVM使用的其余的分代的垃圾回收器也都有寫屏障,舉例來說,每次將一個(gè)老年代對(duì)象的引用修改為指向年輕代對(duì)象,都會(huì)被寫屏障捕獲并記錄下來,因此在年輕代回收的時(shí)候,就可以避免掃描整個(gè)老年代來查找根。

????????G1的垃圾回收器的寫屏障使用一種兩級(jí)的log buffer結(jié)構(gòu):

  • global set of filled buffer:所有線程共享的一個(gè)全局的,存放填滿了的log buffer的集合
  • thread log buffer:每個(gè)線程自己的log buffer。所有的線程都會(huì)把寫屏障的記錄先放進(jìn)去自己的log buffer中,裝滿了之后,就會(huì)把log buffer放到 global set of filled buffer中,而后再申請(qǐng)一個(gè)log buffer;

5、Collect Set:

????????Collect Set(CSet)是指,在 Evacuation 階段,由G1垃圾回收器選擇的待回收的Region集合,在任意一次收集器中,CSet 所有分區(qū)都會(huì)被釋放,內(nèi)部存活的對(duì)象都會(huì)被轉(zhuǎn)移到分配的空閑分區(qū)中。G1 的軟實(shí)時(shí)性就是通過CSet的選擇來實(shí)現(xiàn)的,對(duì)應(yīng)于算法的兩種模式 fully-young generational mode 和 partially-young mode,CSet的選擇可以分成兩種:

  • fully-young generational mode:也稱young GC,該模式下CSet將只包含 young region,G1通過調(diào)整新生代的 region 的數(shù)量來匹配軟實(shí)時(shí)的目標(biāo);
  • partially-young mode:也稱 Mixed GC,該模式會(huì)選擇所有的 young?region,并且選擇一部分的 old region,old region 的選擇將依據(jù)在Marking cycle phase中對(duì)存活對(duì)象的計(jì)數(shù),篩選出回收收益最高的分區(qū)添加到CSet中(存活對(duì)象最少的Region進(jìn)行回收)
  • ????????候選老年代分區(qū)的CSet準(zhǔn)入條件,可以通過活躍度閾值 -XX:G1MixedGCLiveThresholdPercent(默認(rèn)85%) 進(jìn)行設(shè)置,從而攔截那些回收開銷巨大的對(duì)象;同時(shí),每次混合收集可以包含候選老年代分區(qū),可根據(jù)CSet對(duì)堆的總大小占比 -XX:G1OldCSetRegionThresholdPercent(默認(rèn)10%) 設(shè)置數(shù)量上限。

    ????????由上述可知,G1的收集都是根據(jù)CSet進(jìn)行操作的,年輕代收集與混合收集沒有明顯的不同,最大的區(qū)別在于兩種收集的觸發(fā)條件。

    三、G1的的垃圾收集過程:

    ????????G1提供了兩種GC模式,Young GC 和 Mixed GC,兩種都是Stop The World(STW)的,不過講垃圾回收之前,我們先介紹下 G1 的對(duì)象分配策略。

    1、對(duì)象分配策略:

    ????????每一個(gè)分配的 Region 都可以分成兩個(gè)部分,已分配的和未被分配的,它們之間的界限被稱為top。總體上來說,把一個(gè)對(duì)象分配到Region內(nèi),只需要簡(jiǎn)單增加top的值。過程如下:

    (1)線程本地分配緩沖區(qū) Thread Local allocation buffer (TLab):

    ????????如果對(duì)象在一個(gè)共享的空間中分配,那么我們就需要采用同步機(jī)制來解決并發(fā)沖突問題,而為了減少并發(fā)沖突損耗的同步時(shí)間,G1 為每個(gè)應(yīng)用線程和GC線程分配了一個(gè)本地分配緩沖區(qū)TLAB,分配對(duì)象內(nèi)存時(shí),就在這個(gè) buffer 內(nèi)分配,線程之間不再需要進(jìn)行任何的同步,提高GC效率。但是當(dāng)線程耗盡了自己的Buffer之后,需要申請(qǐng)新的Buffer。這個(gè)時(shí)候依然會(huì)帶來并發(fā)的問題。G1回收器采用的是CAS(Compate And Swap)操作。

    ????????顯然的,采用TLAB的技術(shù),就會(huì)帶來碎片。舉例來說,當(dāng)一個(gè)線程在自己的Buffer里面分配的時(shí)候,雖然Buffer里面還有剩余的空間,但是卻因?yàn)榉峙涞膶?duì)象過大以至于這些空閑空間無法容納,此時(shí)線程只能去申請(qǐng)新的Buffer,而原來的Buffer中的空閑空間就被浪費(fèi)了。Buffer的大小和線程數(shù)量都會(huì)影響這些碎片的多寡。

    ? ? ? ? 每次垃圾收集時(shí),每個(gè)GC線程同樣可以獨(dú)占一個(gè)本地緩沖區(qū)(GCLAB)用來轉(zhuǎn)移對(duì)象,將存活對(duì)象復(fù)制到Suvivor空間或老年代空間;

    ????????對(duì)于從Eden/Survivor空間晉升(Promotion)到Survivor/老年代空間的對(duì)象,同樣有GC獨(dú)占的本地緩沖區(qū)進(jìn)行操作,該部分稱為晉升本地緩沖區(qū)(PLAB)。

    (2)Eden區(qū)中分配:

    ????????對(duì)TLAB空間中無法分配的對(duì)象,JVM會(huì)嘗試在Eden空間中進(jìn)行分配。如果Eden空間無法容納該對(duì)象,就只能在老年代中進(jìn)行分配空間。

    (3)Humongous區(qū)分配:

    ????????巨型對(duì)象會(huì)獨(dú)占一個(gè)、或多個(gè)連續(xù)分區(qū),其中第一個(gè)分區(qū)被標(biāo)記為開始巨型(StartsHumongous),相鄰連續(xù)分區(qū)被標(biāo)記為連續(xù)巨型(ContinuesHumongous)。由于無法享受 TLab 帶來的優(yōu)化,并且確定一片連續(xù)的內(nèi)存空間需要掃描整堆,因此確定巨型對(duì)象開始位置的成本非常高,如果可以,應(yīng)用程序應(yīng)避免生成巨型對(duì)象。

    G1內(nèi)部做了一個(gè)優(yōu)化,一旦發(fā)現(xiàn)沒有引用指向巨型對(duì)象,則可直接在年輕代收集周期中被回收。

    2、G1 Young GC:

    ????????當(dāng)Eden區(qū)已滿,JVM分配對(duì)象到Eden區(qū)失敗時(shí),便會(huì)觸發(fā)一次STW式的年輕代收集young GC,將 Eden 區(qū)存活的對(duì)象將被拷貝到 to?survivor 區(qū);from survivor 區(qū)存活的對(duì)象則根據(jù)存活次數(shù)閾值分別晉升到 PLAB、to survivor 區(qū)和老年代中;如果 survivor 空間不夠,Eden區(qū)的部分?jǐn)?shù)據(jù)會(huì)直接晉升到年老代空間。最終Eden空間的數(shù)據(jù)為空,GC停止工作,應(yīng)用線程繼續(xù)執(zhí)行。

    ? ? ? ? young GC 還負(fù)責(zé)維護(hù)對(duì)象的年齡(存活次數(shù)),輔助判斷老化(tenuring)對(duì)象晉升時(shí)的去向。young GC 首先將晉升對(duì)象尺寸總和、年齡信息維護(hù)到年齡表中,再根據(jù)年齡表、Survivor尺寸、Survivor填充容量 -XX:TargetSurvivorRatio(默認(rèn)50%)、最大任期閾值 -XX:MaxTenuringThreshold(默認(rèn)15),計(jì)算出一個(gè)恰當(dāng)?shù)娜纹陂撝?#xff0c;凡是超過任期閾值的對(duì)象都會(huì)被晉升到老年代。

    ????????這時(shí),我們需要考慮一個(gè)問題,如果僅僅 GC 新生代對(duì)象,我們?nèi)绾握业剿械母鶎?duì)象呢? 老年代的所有對(duì)象都是根么?那這樣掃描下來會(huì)耗費(fèi)大量的時(shí)間。于是就需要使用到我們上文介紹到的?RSet 了,RSet 中記錄了其他 region 對(duì)當(dāng)前 region 的引用,因此,在進(jìn)行Young GC 時(shí),掃描根時(shí),僅僅需要掃描這一塊區(qū)域,而不需要掃描整個(gè)老年代。

    2.1、young GC的詳細(xì)回收過程:

    (1)第一階段,根掃描:

    根是指static變量指向的對(duì)象、正在執(zhí)行的方法調(diào)用鏈上的局部變量等。根引用連同 RSet 記錄的外部引用作為掃描存活對(duì)象的入口。

    (2)第二階段,更新RSet:

    處理 dirty card 隊(duì)列中的 card,更新 RSet,此階段完成后,RSet 可以準(zhǔn)確的反映老年代對(duì)所在的region 分區(qū)中對(duì)象的引用

    (3)第三階段:處理RSet:

    識(shí)別被老年代對(duì)象指向的 Eden 中的對(duì)象,這些被指向的Eden中的對(duì)象被認(rèn)為是存活的對(duì)象

    (4)第四階段:對(duì)象拷貝:

    將 Eden 區(qū)存活的對(duì)象將被拷貝到 to?survivor 區(qū);from survivor 區(qū)存活的對(duì)象則根據(jù)存活次數(shù)閾值分別晉升到 PLAB、to survivor 區(qū)和老年代中;如果 survivor 空間不夠,Eden區(qū)的部分?jǐn)?shù)據(jù)會(huì)直接晉升到年老代空間。

    (5)第五階段:處理引用:

    處理軟引用、弱引用、虛引用,最終Eden空間的數(shù)據(jù)為空,GC停止工作,而目標(biāo)內(nèi)存中的對(duì)象都是連續(xù)存儲(chǔ)的、沒有碎片,所以復(fù)制過程可以達(dá)到內(nèi)存整理的效果,減少碎片。

    3、G1 Mixed GC:

    ????????年輕代不斷進(jìn)行垃圾回收活動(dòng)后,為了避免老年代的空間被耗盡。當(dāng)老年代占用空間超過整堆比 IHOP 閾值 -XX:InitiatingHeapOccupancyPercent(默認(rèn)45%)時(shí),G1就會(huì)啟動(dòng)一次混合垃圾回收Mixed GC,Mixed GC不僅進(jìn)行正常的新生代垃圾收集,同時(shí)也回收部分后臺(tái)掃描線程標(biāo)記的老年代分區(qū)。Mixed GC步驟主要分為兩步:

    (1)全局并發(fā)標(biāo)記(global concurrent marking)

    (2)拷貝存活對(duì)象(evacuation)

    這里需要特別注意的是 Mixed GC 并不是 Full GC,只有當(dāng)?Mixed GC 來不及回收old region,也就說在需要分配老年代的對(duì)象時(shí),但發(fā)現(xiàn)沒有足夠的空間,這個(gè)時(shí)候就會(huì)觸發(fā)一次 Full GC

    3.1、全局并發(fā)標(biāo)記(global concurrent marking)

    ????????在進(jìn)行混合回收前,會(huì)先進(jìn)行 global concurrent marking,在 G1 GC 中,它并不是一次GC過程的必須環(huán)節(jié),主要是為 Mixed GC 提供標(biāo)記服務(wù)的。global concurrent marking的執(zhí)行過程分為五個(gè)步驟:

    (1)初始標(biāo)記(initial mark,STW):

    會(huì)標(biāo)記出所有?GC Roots 節(jié)點(diǎn)以及直接可達(dá)的對(duì)象,這一階段需stop the world,但是耗時(shí)很短。

    ????????初始標(biāo)記過程與 young GC 息息相關(guān)。事實(shí)上,當(dāng)達(dá)到 IHOP 閾值時(shí),G1并不會(huì)立即發(fā)起并發(fā)標(biāo)記周期,而是等待下一次年輕代收集,利用年輕代收集的STW時(shí)間段,完成初始標(biāo)記,這種方式稱為借道。

    (2)根區(qū)域掃描(root region scan):

    掃描初始標(biāo)記的存活區(qū)中(即 survivor 區(qū))可直達(dá)的老年代區(qū)域?qū)ο?#xff0c;并標(biāo)記根對(duì)象。該階段與應(yīng)用程序并發(fā)運(yùn)行,并且只有完成該階段后,才能開始下一次 STW 的 young GC。

    因?yàn)?RSet 是不記錄從 young region 出發(fā)的引用,那么就可能出現(xiàn)一種情況,一個(gè)老年代的存活對(duì)象,只被年輕代的對(duì)象引用。在一次young GC中,這些存活的年輕代的對(duì)象會(huì)被復(fù)制到 Survivor Region,因此需要掃描這些 Survivor region 來查找這些指向老年代的對(duì)象的引用,作為并發(fā)標(biāo)記階段掃描老年代的根的一部分。

    (3)并發(fā)標(biāo)記(Concurrent Marking):

    從?GC Roots 對(duì)堆中的對(duì)象進(jìn)行可達(dá)性分析,找出存活的對(duì)象,此過程可能被 young GC 中斷,并發(fā)標(biāo)記階段產(chǎn)生的新的引用(或引用的更新)會(huì)被 SATB 的 write barrier 記錄下來,同時(shí),并發(fā)標(biāo)記線程還會(huì)定期檢查和處理STAB全局緩沖區(qū)列表的記錄,更新對(duì)象引用信息。在此階段中,如果發(fā)現(xiàn)區(qū)域中的所有對(duì)象都是垃圾,那這個(gè)區(qū)域會(huì)立即被回收。同時(shí),并發(fā)標(biāo)記過程中,會(huì)計(jì)算每個(gè)區(qū)域中對(duì)象的存活比例。

    在并發(fā)標(biāo)記階段,我們就不得不了解一下三色標(biāo)記算法,該算法我們放在下文介紹

    (4)重新標(biāo)記(Remark,STW):

    重新標(biāo)記階段是為了修正在并發(fā)標(biāo)記期間,因應(yīng)用程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄,就是去處理剩下的?SATB日志緩沖區(qū)和所有更新,找出所有未被訪問的存活對(duì)象。

    ????????CMS收集器中,重新標(biāo)記使用的增量更新,而 G1 使用的是比 CMS 更快的初始快照算法?SATB 算法:snapshot-at-the-beginning。

    ????????SATB 在標(biāo)記開始時(shí)會(huì)創(chuàng)建一個(gè)存活對(duì)象的快照?qǐng)D,從而確保并發(fā)標(biāo)記階段所有的垃圾對(duì)象都能通過快照被鑒別出來。當(dāng)賦值語(yǔ)句發(fā)生時(shí),應(yīng)用將會(huì)改變了它的對(duì)象圖,那么JVM需要記錄被覆蓋的對(duì)象,因此寫前柵欄會(huì)在引用變更前,將值記錄在SATB日志或緩沖區(qū)中(每個(gè)線程都會(huì)獨(dú)占一個(gè)SATB緩沖區(qū),初始有256條記錄空間)。當(dāng)空間用盡時(shí),線程會(huì)分配新的SATB緩沖區(qū)繼續(xù)使用,而原有的緩沖去則加入全局列表中。最終在并發(fā)標(biāo)記階段,并發(fā)標(biāo)記線程在標(biāo)記的同時(shí),還會(huì)定期檢查和處理全局緩沖區(qū)列表的記錄,然后根據(jù)標(biāo)記位圖分片的標(biāo)記位,掃描引用字段來更新RSet,修正 SATB? 的誤差。

    ????????SATB的 log buffer 如 RSet 的寫屏障使用的 log buffer 一樣,都是兩級(jí)結(jié)構(gòu),作用機(jī)制也是一樣的。

    (5)清除(Cleanup,STW):

    該階段主要是排序各個(gè)?Region 的回收價(jià)值和成本,并根據(jù)用戶所期望的GC停頓時(shí)間來制定回收計(jì)劃。(這個(gè)階段并不會(huì)實(shí)際去做垃圾的回收,也不會(huì)執(zhí)行存活對(duì)象的拷貝)

    清除階段執(zhí)行的詳細(xì)操作有一下幾點(diǎn):

    ① RSet梳理:啟發(fā)式算法會(huì)根據(jù)活躍度和RSet尺寸對(duì)分區(qū)定義不同等級(jí),同時(shí)RSet數(shù)理也有助于發(fā)現(xiàn)無用的引用。

    ② 整理堆分區(qū):為混合收集識(shí)別回收收益高(基于釋放空間和暫停目標(biāo))的老年代分區(qū)集合;

    ③ 識(shí)別所有空閑分區(qū):即發(fā)現(xiàn)無存活對(duì)象的分區(qū),該分區(qū)可在清除階段直接回收,無需等待下次收集周期。

    如果不考慮維護(hù)Remembered Set的操作,可以分為上圖4個(gè)步驟(與CMS較為相似),其中初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記跟CMS收集器相同,只有第四階段的篩選回收有些區(qū)別。

    3.2、拷貝存活對(duì)象(evacuation):

    當(dāng)G1發(fā)起全局并發(fā)標(biāo)記之后,并不會(huì)馬上開始混合收集,G1會(huì)先等待下一次年輕代收集,然后在該 young gc 收集階段中,確定下次混合收集的CSet

    ? ? ? ? 全局標(biāo)記完成后,G1 就知道哪些 old region 的可回收垃圾最多了,只需等待合適的時(shí)機(jī)就可以開始混合回收了,而混合回收除了回收這個(gè)young region,還會(huì)回收部分 old region(不需要回收全部 old region)。根據(jù)停頓目標(biāo),G1 可能沒法一次回收掉所有的old region 候選分區(qū),只能選擇優(yōu)先級(jí)高的若干個(gè) region 進(jìn)行回收,所以G1可能會(huì)產(chǎn)生連續(xù)多次的混合收集與應(yīng)用線程交替執(zhí)行,而這些被選中的 region 就是 CSet 了,而單次的混合回收的算法與上文的 Young GC 算法完全一樣,只不過回收集CSet 中多了老年代的內(nèi)存分段;而第二個(gè)步驟就是將這些 region 中存活的對(duì)象復(fù)制到空間 region 中去,同時(shí)把這些已經(jīng)被回收的 region 放到空閑 region 列表中。

    ????????G1會(huì)計(jì)算每次加入到CSet中的分區(qū)數(shù)量、混合收集進(jìn)行次數(shù),并且在上次的年輕代收集、以及接下來的混合收集中,G1會(huì)確定下次加入CSet的分區(qū)集(Choose CSet),并且確定是否結(jié)束混合收集周期。

    (1)并發(fā)標(biāo)記結(jié)束以后,老年代中100%為垃圾的 region 就直接被回收了,僅部分為垃圾的region會(huì)被分成8次回收(可以通過 -XX:G1MixedGCCountTarget 設(shè)置,默認(rèn)閾值8),所以 Mixed GC 的回收集(CSet)包括八分之一的老年代內(nèi)存分段、Eden 區(qū)內(nèi)存分段、Survivor 區(qū)內(nèi)存分段。

    (2)由于老年代的內(nèi)存分段默認(rèn)分8次回收,G1會(huì)優(yōu)先回收垃圾多的內(nèi)存分段。垃圾占內(nèi)存分段比例越高的,越會(huì)被先回收。并且由一個(gè)閾值決定內(nèi)存分段是否被回收?-XX:G1MixedGCLiveThresholdPercent,默認(rèn)為 65%,意思是垃圾占內(nèi)存分段比例要達(dá)到 65% 才會(huì)被回收。如果垃圾占比太低,意味著存活的對(duì)象占比高,在復(fù)制的時(shí)候會(huì)花費(fèi)更多的時(shí)間。

    (3)混合回收并不一定要進(jìn)行8次,有一個(gè)閾值?-XX:G1HeapWastePercent,默認(rèn)值 10%,意思是允許整個(gè)堆內(nèi)存有 10% 的空間浪費(fèi),意味著如果發(fā)現(xiàn)可以回收的垃圾占堆內(nèi)存的比例低于10%,則不再進(jìn)行混合回收,因?yàn)?GC 會(huì)花費(fèi)很多的時(shí)間,但是回收到的內(nèi)存卻很少

    G1 垃圾回收流程小結(jié):Young CG 和 Mixed GC,是G1回收空間的主要活動(dòng)。當(dāng)應(yīng)用開始運(yùn)行時(shí),堆內(nèi)存可用空間還比較大,只會(huì)在年輕代滿時(shí),觸發(fā)年輕代收集;隨著老年代內(nèi)存增長(zhǎng),當(dāng)?shù)竭_(dá) IHOP 閾值 -XX:InitiatingHeapOccupancyPercent(老年代占整堆比,默認(rèn)45%) 時(shí),G1開始著手準(zhǔn)備收集老年代空間。首先經(jīng)歷并發(fā)標(biāo)記周期,識(shí)別出高收益的老年代分區(qū)。但隨后G1并不會(huì)馬上開啟一次混合收集,而是讓應(yīng)用線程先運(yùn)行一段時(shí)間,等待觸發(fā)一次年輕代收集,在這次STW中,G1將開始整理混合收集周期。接著再次讓應(yīng)用線程運(yùn)行,當(dāng)接下來的幾次年輕代收集時(shí),將會(huì)有老年代分區(qū)加入到CSet中,即觸發(fā)混合收集,這些連續(xù)多次的混合收集稱為混合收集。

    4、Full GC:

    ????????當(dāng) G1 無法在堆空間中申請(qǐng)新的分區(qū)時(shí),G1便會(huì)觸發(fā)擔(dān)保機(jī)制,執(zhí)行一次STW式的、單線程的 Full GC,Full GC會(huì)對(duì)整堆做標(biāo)記清除和壓縮,最后將只包含純粹的存活對(duì)象。參數(shù)-XX:G1ReservePercent(默認(rèn)10%)可以保留空間,來應(yīng)對(duì)晉升模式下的異常情況,最大占用整堆50%,更大也無意義。

    ????????G1在以下場(chǎng)景中會(huì)觸發(fā) Full GC,同時(shí)會(huì)在日志中記錄to-space-exhausted以及Evacuation Failure:

    • (1)從年輕代分區(qū)拷貝存活對(duì)象時(shí),無法找到可用的空閑分區(qū)
    • (2)從老年代分區(qū)轉(zhuǎn)移存活對(duì)象時(shí),無法找到可用的空閑分區(qū)
    • (3)分配巨型對(duì)象時(shí)在老年代無法找到足夠的連續(xù)分區(qū)

    ????????由于G1的應(yīng)用場(chǎng)合往往堆內(nèi)存都比較大,所以Full GC的收集代價(jià)非常昂貴,應(yīng)該避免Full GC的發(fā)生。

    5、三色標(biāo)記算法:

    三色標(biāo)記算法是并發(fā)收集階段的重要算法,它是描述追蹤式回收器的一種有用的方法,利用它可以推演回收器的正確性。 首先,我們將對(duì)象分成三種類型的。

    • 黑色:根對(duì)象,或者該對(duì)象與它的子對(duì)象都被掃描了
    • 灰色:對(duì)象本身被掃描,但還沒掃描完該對(duì)象中的子對(duì)象
    • 白色:未被掃描對(duì)象,掃描完成所有對(duì)象之后,最終為白色的為不可達(dá)對(duì)象,即垃圾對(duì)象

    下面我們就以一組演變圖,加深下對(duì)三色標(biāo)記算法的理解,當(dāng)GC開始掃描對(duì)象時(shí),按照如下圖步驟進(jìn)行對(duì)象的掃描:

    5.1、三色標(biāo)記算法的正常流程:

    (1)根對(duì)象被置為黑色,子對(duì)象被置為灰色:

    ?(2)繼續(xù)由灰色遍歷,將已掃描了子對(duì)象的對(duì)象置為黑色。

    (3)遍歷了所有可達(dá)的對(duì)象后,所有可達(dá)的對(duì)象都變成了黑色。不可達(dá)的對(duì)象即為白色,需要被清理。

    5.2、三色標(biāo)記算法的異常情況:

    ????????如果在標(biāo)記過程中,應(yīng)用程序也在運(yùn)行,那么對(duì)象的指針就有可能改變。這樣的話,我們就會(huì)遇到一個(gè)問題:對(duì)象丟失問題。我們看下面一種情況,當(dāng)垃圾收集器掃描到下面情況時(shí):

    ?這時(shí)候應(yīng)用程序執(zhí)行了以下操作:

    A.c=C
    B.c=null

    這樣,對(duì)象的狀態(tài)圖變成如下情形:

    ?這時(shí)候垃圾收集器再標(biāo)記掃描的時(shí)候就會(huì)下圖成這樣:

    很顯然,此時(shí)C是白色,被認(rèn)為是垃圾需要清理掉,顯然這是不合理的。那么我們?nèi)绾伪WC應(yīng)用程序在運(yùn)行的時(shí)候,GC標(biāo)記的對(duì)象不丟失呢?有如下兩種可行的方式:

    • 在插入的時(shí)候記錄對(duì)象
    • 在刪除的時(shí)候記錄對(duì)象

    剛好這對(duì)應(yīng) CMS?和 G1 的兩種不同實(shí)現(xiàn)方式:

    (1)CMS采用的是增量更新(Incremental update):只要在寫屏障里發(fā)現(xiàn)要有一個(gè)白對(duì)象的引用被賦值到一個(gè)黑對(duì)象的字段里,那就把這個(gè)白對(duì)象變成灰色的,即插入的時(shí)候記錄下來。

    (2)G1 使用的是 STAB(snapshot-at-the-beginning)的方式:刪除的時(shí)候記錄所有的對(duì)象,它有三個(gè)步驟:

    • ① 在開始標(biāo)記的時(shí)候生成一個(gè)存活對(duì)象的快照?qǐng)D
    • ② 在并發(fā)標(biāo)記時(shí),所有被改變的對(duì)象入隊(duì)(在write barrier里把所有舊的引用所指向的對(duì)象都變成非白的)
    • ③ 可能存在游離的垃圾,將在下次被收集

    四、G1 收集器的缺點(diǎn):

    (1)如果停頓時(shí)間過短的話,可能導(dǎo)致每次選出的回收集只占堆內(nèi)存很小一部分,收集器收集的速度逐漸跟不上分配器的分配速度,進(jìn)而導(dǎo)致垃圾慢慢堆積,最終造成堆空間占滿,引發(fā)Full GC 反而降低性能。

    (2)G1 無論是在垃圾收集產(chǎn)生的內(nèi)存占用還是程序運(yùn)行時(shí)的額外執(zhí)行負(fù)載都要比CMS要高

    (3)CMS在小內(nèi)存應(yīng)用上大概率由于 G1。所以小內(nèi)存的情況下使用CMS收集器,大內(nèi)存的情況下可以使用G1收集器(G1收集器6GB以上)

    參考文章:

    https://www.cnblogs.com/lsgxeva/p/10231201.html

    https://blog.csdn.net/weixin_43899069/article/details/117996701

    https://blog.csdn.net/coderlius/article/details/79272773

    https://www.jianshu.com/p/aef0f4765098

    總結(jié)

    以上是生活随笔為你收集整理的G1 垃圾收集器原理详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。