G1垃圾回收器介紹與CMS區別

豆芽001發表於2021-01-27

1. Region分割槽

G1之前的垃圾收集器,將堆區主要劃分了Eden區,Old區,Survivor區。其中對於Eden,Survivor對回收過程來說叫做“年輕代垃圾收集”。並且年輕代和老年代都分別是連續的記憶體空間。G1將堆分成了若干Region,Region的大小可以透過G1HeapRegionSize引數進行設定,其必須是2的冪,範圍允許為1Mb到32Mb。 JVM的會基於堆記憶體的初始值和最大值的平均數計算分割槽的尺寸,平均的堆尺寸會分出約2000個Region。分割槽大小一旦設定,則啟動之後不會再變化。

Eden regions(年輕代-Eden區)

Survivor regions(年輕代-Survivor區)

Old regions(老年代)

Humongous regions(巨型物件區域)

Free resgions(未分配區域,也會叫做可用分割槽)

1)G1還是採用分代回收,但是不同的分代之間記憶體不一定是連續的,不同分代的Region的佔用數也不一定是固定的(不建議透過相關選項顯式設定年輕代大小。會覆蓋暫停時間目標)。年輕代的Eden,Survivor數量會隨著每一次GC發生相應的改變。

2)分割槽是不固定屬於哪個分代的,所以比如一次ygc過後,原來的Eden的分割槽就會變成空閒的可用分割槽,隨後也可能被用作分配巨型物件,成為H區等。

3)G1中的巨型物件是指,佔用了Region容量的50%以上的一個物件。Humongous區,就專門用來儲存巨型物件。如果一個H區裝不下一個巨型物件,則會透過連續的若干H分割槽來儲存。因為巨型物件的轉移會影響GC效率,所以併發標記階段發現巨型物件不再存活時,會將其直接回收。ygc也會在某些情況下對巨型物件進行回收。

4)分割槽可以有效利用記憶體空間,因為收集整體是使用“標記-整理”,Region之間基於“複製”演算法,GC後會將存活物件複製到可用分割槽(未分配的分割槽),所以不會產生空間碎片。

5)G1類似CMS,也會在比如一次fullgc中基於堆尺寸的計算重新調整(增加)堆的空間。但是相較於執行fullgc,G1 GC會在無法分配物件或者巨型物件無法獲得連續分割槽來分配空間時,優先嚐試擴充套件堆空間來獲得更多的可用分割槽。原則上就是G1會計算執行GC的時間,並且極力減少花在GC上的時間(包括ygc,mixgc),如果可能,會透過不斷擴充套件堆空間來滿足物件分配、轉移的需要。

6)因為G1提供了“可預測的暫停時間”,也是基於G1的啟發式演算法,所以G1會估算年輕代需要多少分割槽,以及還有多少分割槽要被回收。younggc觸發的契機就是在Eden分割槽數量達到上限時。一次younggc會回收所有的Eden和survivor區。其中存活的物件會被轉移到另一個新的survivor區或者old區,如果轉移的目標分割槽滿了,會再將可用區標記成S或者O區。

2. G1重要的資料結構

TLAB(Thread Local Allocation Buffer)本地執行緒緩衝區

G1 GC會預設會啟用Tlab最佳化。其作用就是在併發情況下,基於CAS的獨享執行緒(mutator threads)可以優先將物件分配在一塊記憶體區域(屬於Java堆的Eden中),只是因為是Java執行緒獨享的記憶體區,沒有鎖競爭,所以分配速度更快,每個Tlab都是一個執行緒獨享的。如果待分配的物件被判斷是巨型物件,則不使用TLAB。

PLAB(Promotion Local Allocation Buffer) 晉升本地分配緩衝區

younggc中,物件會將全部Eden區存活的物件轉移(複製)到S區分割槽。也會存在S區物件晉升(Promotion)到老年代。這個決定晉升的閥值可以透過MaxTenuringThreshold設定。晉升的過程,無論是晉升到S還是O區,都是在GC執行緒的PLAB中進行。每個GC執行緒都有一個PLAB。

Collection Sets(CSets)待收集集合

GC中待回收的region的集合。CSet中可能存放著各個分代的Region。CSet中的存活物件會在gc中被移動(複製)。GC後CSet中的region會成為可用分割槽。

Remembered Sets(RSets)已記憶集合

已記憶集合在每個分割槽中都存在,並且每個分割槽只有一個RSet。其中儲存著其他分割槽中的物件對本分割槽物件的引用,是一種points-in結構。ygc的時候,只要掃描RSet中的old區物件對於本young區的引用,不 需要掃描所有old區。mixed gc時,掃描Old區的RSet中,其他old區對於本old分割槽的引用,一樣不用掃描所有的old區。提高了GC效率。因為每次GC都會掃描所有young區物件,所以RSet只有在掃描old引用young,old引用old時會被使用。

Card Table 卡表

Java堆劃分為相等大小的一個個區域,這個小的區域(一般size在128-512位元組)被當做Card,而Card Table維護著所有的Card。Card Table的結構是一個位元組陣列,Card Table用單位元組的資訊對映著一個Card。當Card中儲存了物件時,稱為這個Card被髒化了(dirty card)。 對於一些熱點Card會存放到Hot card cache。同Card Table一樣,Hot card cache也是全域性的結構。

3. G1與CMS的對比

3.1 CMS處理過程

CMS收集器僅作用於老年代的收集,是基於標記-清除演算法的,它的運作過程分為4個步驟:

初始標記(CMS initial mark)獨佔CPU(STW),僅標記GCroots能直接關聯的物件

併發標記(CMS concurrent mark)可以和使用者執行緒並行執行,標記所有可達物件

重新標記(CMS remark)獨佔CPU(STW),對併發標記階段使用者執行緒執行產生的垃圾物件進行標記修正

併發清除(CMS concurrent sweep)可以和使用者執行緒並行執行,清理垃圾

其中,初始標記、重新標記這兩個步驟仍然需要Stop-the-world。初始標記僅僅只是標記一下GC Roots能直接關聯到的物件,速度很快,併發標記階段就是進行GC Roots Tracing的過程,而重新標記階段則是為了修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始階段稍長一些,但遠比並發標記的時間短。

3.2 CMS 優點

併發收集、低停頓

3.3 CMS缺點

1)對CPU非常敏感:在併發階段雖然不會導致使用者執行緒停頓,但是會因為佔用了一部分執行緒使應用程式變慢

2)無法處理浮動垃圾:在最後一步併發清理過程中,使用者執行緒執行也會產生垃圾,但是這部分垃圾是在標記之後,所以只有等到下一次gc的時候清理掉,這部分垃圾叫浮動垃圾

3)CMS使用“標記-清理”法會產生大量的空間碎片:當碎片過多,將會給大物件空間的分配帶來問題,會出現老年代還有很大的空間但無法找到足夠大的連續空間來分配當前物件,不得不提前觸發一次FullGC,為了解決這個問題CMS提供了一個開關引數(-XX:+UseCMSCompactAtFullCollection預設開啟),用於在FullGC完成之後進行一次碎片整理,但是記憶體整理的過程是無法併發的,會導致停頓時間變長

3.4 G1 YoungGC

年輕代垃圾回收只會回收Eden區和Survivor區。YGC時,首先G1停止應用程式的執行(Stop-The-World),G1建立回收集(Collection Set),回收集是指需要被回收的記憶體分段的集合,年輕代回收過程的回收集包含年輕代Eden區和Survivor區所有的記憶體分段。

1)第一階段,掃描根。根是指static變數指向的物件,正在執行的方法呼叫鏈條上的區域性變數等。跟引用連同RSet記錄的外部引用作為掃描存活物件的入口。

2)第二階段,更新RSet。處理dirty card queue中的card,更新RSet。此階段完成後,RSet可以準確的反映老年代對所在的記憶體分段中物件的引用。

3)第三階段,處理RSet。識別被老年代物件指向的Eden中的物件,這些被指向的Eden中的物件被認為是存活的物件。
4)第四階段,複製物件。此階段,物件樹被遍歷,Eden區記憶體段中存活的物件會被複制到Survivor區中空的記憶體分段,Survivor區記憶體段中存活的物件如果年齡未達閾值,年齡會加1,達到閾值會被複制到Old區中空的記憶體分段。如果Survivor空間不夠,Eden空間的部分資料會直接晉升到老年代空間。

5)第五階段,處理引用。處理Soft,Weak,Phantom,Final,JNI Weak 等引用。最終Eden空間的資料為空,GC停止工作,而目標記憶體中的物件都是連續儲存的,沒有碎片,所以複製過程可以達到記憶體整理的效果,減少碎片。

3.5 G1 併發標記

當整個堆大小在jvm堆疊空間中佔比達到IHOP閾值-XX:InitiatingHeapOccupancyPercent(預設45%)時,G1就會啟動一次混合垃圾收集週期。Mix GC不僅進行正常的新生代垃圾收集,同時也回收部分後臺掃描執行緒標記的老年代分割槽。進行Mix GC之前,會先進行全域性併發標記。

1)初始標記(InitingMark):標記GC Roots,會STW,一般會複用YoungGC的暫停時間。初始標記會設定好所有分割槽的NTAMS值。

2)根分割槽掃描(RootRegionScan):根據初始標記階段確定的GC根元素,掃描這些元素所在region,獲取對老年代的引用,並標記被引用的物件。 該階段與應用執行緒併發執行,也就是說沒有STW停頓,必須在下一次年輕代GC開始之前完成。

3)併發標記(ConcurrentMark):遍歷整個堆,查詢所有可達的存活物件。若發現區域物件中的所有物件都是垃圾,那這個區域會被立即回收。 此階段與應用執行緒併發執行, 也允許被年輕代GC打斷。

4)最終標記(Remark):此階段有一次STW暫停,以完成標記週期。 G1會清空SATB緩衝區,跟蹤未訪問到的存活物件,並進行引用處理。

5)清除階段(Clean UP): 這是最後的子階段,G1在執行統計和清理RSet時會有一次STW停頓。 在統計過程中,會把完全空閒的region標記出來,也會標記出適合於進行混合模式GC的候選region。 清理階段有一部分是併發執行的,比如在重置空閒region並將其加入空閒列表時。

清除階段之後,還會對存活物件進行轉移(複製演算法),轉移到其他可用分割槽,所以當前的分割槽就變成了新的可用分割槽。複製轉移主要是為了解決分割槽內的碎片問題。

3.6 G1MixedGC

1)併發標記結束以後,老年代中百分百為垃圾的記憶體分段被回收了,部分為垃圾的記憶體分段被計算了出來。預設情況下,這些老年代的記憶體分段會分8次(可以透過-XX:G1MixedGCCountTarget設定)被回收。

2)混合回收的回收集(Collection Set)包括八分之一的老年代記憶體分段,Eden區記憶體分段,Survivor區記憶體分段。混合回收的演算法和年輕代回收的演算法完全一樣,只是回收集多了老年代的記憶體分段。具體過程請參考年輕代回收過程。

3)由於老年代中的記憶體分段預設分8次回收,G1會優先回收垃圾多的記憶體分段。垃圾佔記憶體分段比例越高,越會被先回收。並且有一個閾值會決定記憶體分段是否被回收。-XX:G1MixedGCLiveThresholdPercent,預設為65%,意思是垃圾佔記憶體分段比例要達到65%才會被回收。如果垃圾佔比太低,意味著存活的物件佔比高,在複製的時候會花費更多的時間。

4)混合回收並不一定要進行8次。有一個閾值-XX:G1HeapWastePercent,預設值為10%,意思是允許整個堆記憶體中有10%的空間被浪費,意味著如果發現可以回收的垃圾佔堆記憶體的比例低於10%,則不再進行混合回收。因為GC會花費很多的時間但是回收到的記憶體卻很少。

3.7 G1特點

1)並行與併發:G1能充分利用多CPU、多核環境下的硬體優勢,使用多個CPU來縮短Stop-the-world停頓的時間,部分其他收集器原來需要停頓Java執行緒執行的GC操作,G1收集器仍然可以透過併發的方式讓Java程式繼續執行。

2)分代收集

3)空間整合:與CMS的標記-清除演算法不同,G1從整體來看是基於標記-整理演算法實現的收集器,從區域性(兩個Region之間)上來看是基於“複製”演算法實現的。但無論如何,這兩種演算法都意味著G1運作期間不會產生記憶體空間碎片,收集後能提供規整的可用記憶體。這種特性有利於程式長時間執行,分配大物件時不會因為無法找到連續記憶體空間而提前觸發下一次GC。

4)可預測的停頓:這是G1相對於CMS的另一大優勢,降低停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用這明確指定一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69990087/viewspace-2753935/,如需轉載,請註明出處,否則將追究法律責任。

相關文章