深刻理解垃圾收集器的G1及日誌分析

儘管Hotspot 最新的垃圾回收器G1是在2006年推出的。可是G1從推行至今的市場反響來看,但如今足以證實這款垃圾收集器是經得起考驗的,從java9開始,就默認爲G1垃圾收集器。G1是一款面向服務端應用的垃圾收集器。HotSpot開發團隊賦予它的使命是(在比較長期的)將來能夠替換掉JDK1.5中發佈的CMS收集器。與其餘GC收集器相比,G1具有以下特色。java

並行與併發、分代收集的垃圾收集算法、可預測的停頓、空間整合。算法

特色

從分代來看,G1依然屬於分代垃圾收集器,她會區分年輕代和老年代,依然有eden區和survivor區,但從堆的結構來看,它並不要求整個eden、年輕代或者老年代都連續,它使用了分區算法。bash

並行性: 在回收期間,能夠由多個GC線程同時工做,有效利用多核計算能力。併發

井發性: GI 擁有與應用程序交替執行的能力,部分工做能夠和應用程序同時執行,所以一 般來講,不會在整個回收期間徹底阻塞應用程序。性能

分代 GC : Gl 依然是一個分代收集器,可是和以前回收器不一樣,它同時兼顧年輕代和老年代。對比其餘回收器,它們或者工做在年輕代,或者工做在老年代。所以,這裏是一個很大的不一樣。spa

空間整理: Gl 在回收過程當中,會進行適當的對象移動,不像CMS,只是簡單地標記清理對象,在若干次 GC 後,CMS 必須進行一次碎片整理。而Gl不一樣,它每次回收都會有效地複製對象,減小空間碎片。.net

G1把內存「化整爲零」的思路,理解起來彷佛很容易,但其中的實現細節卻遠遠沒有想象中那樣簡單,不然也不會從2004年Sun實驗室發表第一篇G1的論文開始直到今天(將近10年時間)纔開發出G1的商用版。線程

筆者以一個細節爲例:把Java堆分爲多個Region後,垃圾收集是否就真的能以Region爲單位進行了?聽起來瓜熟蒂落,再仔細想一想就很容易發現問題所在:Region不多是孤立的。一個對象分配在某個Region中,它並不是只能被本Region中的其餘對象引用,而是能夠與整個Java堆任意的對象發生引用關係。那在作可達性斷定肯定對象是否存活的時候,豈不是還得掃描整個Java堆才能保證準確性?這個問題其實並不是在G1中才有,只是在G1中更加突出而已。在之前的分代收集中,新生代的規模通常都比老年代要小許多,新生代的收集也比老年代要頻繁許多,那回收新生代中的對象時也面臨相同的問題,若是回收新生代時也不得不一樣時掃描老年代的話,那麼Minor GC的效率可能降低很多。3d

​ 在G1收集器中,Region之間的對象引用以及其餘收集器中的新生代與老年代之間的對象引用,虛擬機都是使用Remembered Set來避免全堆掃描的。G1中每一個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型的數據進行寫操做時,會產生一個Write Barrier暫時中斷寫操做,檢查Reference引用的對象是否處於不一樣的Region之中(在分代的例子中就是檢查是否老年代中的對象引用了新生代中的對象),若是是,便經過CardTable把相關引用信息記錄到被引用對象所屬的Region的RememberedSet之中。當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set便可保證不對全堆掃描也不會有遺漏。日誌

G1的內存劃分和主要收集過程

G1收集器講堆進行分區,劃分爲一個個區域,每次收集時,只收集其中幾個區域,以此來控制垃圾回收產生的一次停頓時間。

G1收集過程四個階段:

新生代GC(YGC) 併發標記週期 混合收集 若是須要進行full GC

G1的新生代GC

新生代GC的主要工做是回收eden區和survivor區。 一旦eden 區被佔滿,新生代GC就會啓動。新生代GC收集先後的堆數據如圖5.6所示,其中E表示eden區,S表示survivor區, o表示老年代。能夠看到,新生代GC只處理eden和survivor區,回收後,全部的eden區都應該被消空,而survivor區會被收集一 部分數據,可是應該至少仍然存在一 個 survivor 區,類比其餘的新生代收集器,這一 點彷佛並無太大變化。另外一 個亟要的變化是老年代的區域增多,由於部分survivor區或者eden區的對象可能會晉升到老年代。

從日誌中能夠看到,eden區本來佔用235MB空間,回收後被清空,survivor區從5MB增加到了11MB, 這是由於部分對象從eden區複製到survivor區,整個堆合計爲400MB, 從回收前的239MB降低到10.5MB。

G1的井發標記週期

G1的併發階段與CMS有點相似,他們都是爲了下降一次停頓時間,而將能夠和應用程序併發的部分單獨提取出來執行。

併發標記週期能夠分爲如下幾步。若是不計算維護RememberedSet的操做,G1收集器的運做大體可劃分爲如下幾個步驟:

初始標記(Initial Marking)
根區域掃描
併發標記(Concurrent Marking)
最終標記(Final Marking)
獨佔清理 
篩選回收(Live Data Counting and Evacuation)
複製代碼

初始標記: 標記從根節點直接可達的對象。這個階段會伴隨一次新生代GC,它會產生全局停頓。

根區域掃描: 因爲初始標記必然會伴隨一次新生代的GC,因此在初始化標記後,eden被清空,而且存活對象被移入survivor區。這個階段,將掃面survivor區直接可達的老年代對象,並標記這些直接可達的對象。根區域掃描不能和新生代GC同時執行。

併發標記: 和CMS相似,掃面查找整個對存活的對象,這是一個併發的過程,能夠被一次新生代GC打斷。

從新標記: 因爲併發標記過程當中,應用仍在執行,所以標記結果須要修正,因此對上一次的標記結果進行補充,在G1中,這個過程使用STAB算法完成。即G1會在標記之初爲存活對象建立一個快照,有助於加速從新標記速度。

獨佔清理: 這個階段會引發停頓。

併發清理階段: 識別並清理徹底空閒的區域。它是併發的清理,不會引發停頓。

在併發標記週期時,G1會產生以下日誌:

(1)、初始標記,伴隨一次新生代GC

[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0117414 secs]
    ...
   [Eden: 1024.0K(4096.0K)->0.0B(2048.0K) Survivors: 2048.0K->1024.0K Heap: 9581.8K(20.0M)->12.3M(20.0M)]
 [Times: user=0.11 sys=0.00, real=0.01 secs] 
複製代碼

能夠看到初始化標記時,eden被清空,並部分複製到survivor區

(2)、這是一次併發的根區域掃描,併發掃面過程當中,不能被新生代GC打斷。

[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0007368 secs]
複製代碼

(3)、下面這個是指併發標記

[GC concurrent-mark-start]
[GC pause (G1 Evacuation Pause) (young) (to-space exhausted), 0.0427113 secs]
    .....
   [Eden: 2048.0K(2048.0K)->0.0B(1024.0K) Survivors: 1024.0K->1024.0K Heap: 16.4M(20.0M)->18.0M(20.0M)]
 [Times: user=0.05 sys=0.02, real=0.04 secs] 
 ...
 [GC concurrent-mark-abort]
複製代碼

(4)、從新標記引發全局停頓

[Ref Proc: 0.3 ms]
複製代碼

(5)、從新標記後進行獨佔清理

4.088: [GC cleanup 117M->106M(138M), 0.0015198 secs]
複製代碼

(6)、併發清理是併發執行的,會根據獨佔清理階段計算出的每一個區域的存活對象數量,直接回收已經不包含存活對象的區域。

4.090: [GC concurrent-cleanup-start]

併發清理階段開始,它釋放被發現爲空的區域(不包含任何的活躍數據的區域),在上一個stop-the-world階段期間。

4.091: [GC concurrent-cleanup-end, 0.0002721]

併發清理階段清理空的區域用時0.0002721秒。
複製代碼

關於G1日誌,想要知道全部內容的,能夠看這篇文章,適合查詢: blog.csdn.net/zhanggang80…

混合回收

在併發標記週期中,雖然有部分對象被回收,可是從總體上來講,回收的比例是至關低的。可是在併發標記週期後,G1已經明確知道哪些區域含有比較多的垃圾對象了,在混合階段,就是對這些區域進行回收的。固然,會優先回收垃圾比例比較高的區域。由於回收這些區域的性價比比較高。

G1是指垃圾優先的垃圾回收器,全稱"Garbage First Garbage Collector".

在混合回收中,即會執行正常年輕代GC,也會選取被標記的老年代區域進行回收,它同時處理了新生代和老年代。由於新生代GC的緣由,eden區域必然被清空,此外,以下圖,有兩塊區域被標記爲G的垃圾回收比例最高的區域被清理。被清理區域的存活對象會被移動到其餘區域,這樣的好處是能夠減小空間碎片。

混合GC產生以下日誌:

混合GC執行屢次,直到回收了足夠多的內存空間,觸發一次新生代GC。新生代GC後,有可能會發生一次併發標記週期的清理,最後又引發混合GC。整個流程見下圖:

必要時的Full GC

與CMS相似,併發收集因爲讓應用和GC線程交替工做,所以老是不能避免在特別的繁忙場合在回收過程致使內存不足,此時,G1也會執行一個Full GC回收。

此外,在混合GC和新生代GC時,survivor與老年代沒法沒法容納倖存對象,都會致使Full GC產生

完整的G1日誌分析

G1 的相關參數

對於Gl收集器,可使用-XX:+UseGIGC標記打開Gl收集器開關,對Gt收集器進行設置時,最重要的一 個參數就是-XX :MaxGCPauseMillis,它用於指定目標最大停頓時間。若是任何一次停頓超過這個設置值時,Gl就會嘗試調整新生代和老年代的比例、調整堆大小、調整晉升年齡等手段,試圖達到預設目標。對於性能調優來講,

有時候,老是魚和熊掌不可兼得的,若是停頓時間縮短,對千新生代來講,這意味着極可能要增長新生代GC的次數,GC反而會變得更加頻繁。對於老年代區域來講,爲了得到更短的停頓時間,那麼在混合GC收集時,一次收集的區域數量也會變少,這樣無疑增長了進行FullGC的可能性。另一個重要的參數是-XX :ParallelGCThreads, 它用於設置並行回收時,GC的工做線程數量。

此外,-XX:InitiatingHeapOccupancyPercent參數能夠指定當整個堆使用率達到多少時,觸發併發標記週期的執行。默認值是45, 即當整個堆佔用率達到45%時,執行併發標記週期。 InitiatingHeapOccupancyPercent 一 旦設置,始終都不會被G l收集器修改,這意味着G I收集器不會試圖改變這個值,來知足MaxGCPause汕His的目標。若是InitiatingHeapOccupancyPercent值設置偏大,會致使併發週期遲遲得不到啓動,那麼引發Full GC的可能性也大大增長,反之,一 個太小的 InitiatingHeapOccupancyPercent值,會使得併發週期很是頻繁,大整 GC 線程搶佔CPU, 會致使應用程序的性能有所降低。

來自《深刻理解JVM虛擬機》JVM高級特性與最佳實現。

《實戰java虛擬機》複製代碼
相關文章
相關標籤/搜索