原文出處:Tips for Tuning the Garbage First Garbage Collectorjava
這是由兩部分組成的系列的第二篇關於G1垃圾回收器的文章,你能夠在2013.07.17的InfoQ上找到第一部分:G1: One Garbage Collector To Rule Them All。併發
在我咱們瞭解如何調整G1 GC以前,首先我必須瞭解G1定義的關鍵概念。在這篇文章裏,我會首先介紹概念,而後討論如何調整G1(適當的時候)。 post
Remembered Setsspa
從以前的文章回憶起它:Remembered Sets(RSets)是每個region裏面幫助G1 GC追蹤外部指向這個region的引用。所以如今,取代由於引用指向這個region掃描整個heap區,G1只須要掃描RSets。命令行
1: Remembered Sets線程
咱們看一下示意圖。上面的示意圖向咱們展現三個region(灰色)。Region 1, Region 2和Region 3和它們關聯的RSets(粉紅色),RSets表明一些card的集合。Region 1和Region 3剛好引用Region2裏的對象。所以,Region 2的RSets記錄了兩個引用Region2的對象,Region2就是「owning region」。3d
這裏有兩個概念幫助理解RSets:日誌
Post-write barrierscode
Concurrent refinement threadsorm
屏障代碼在寫操做以後(所以名稱是「post-write barrier」),爲了記錄幫助追蹤跨region更新。包含更新引用字段的card更新可靠的日誌緩衝區。一旦這些緩衝區滿了,它們就中止工做。Concurrent refinement threads處理這些緩衝區日誌。
注意到Concurrent refinement threads經過併發更新它們來幫助維護RSets(一般在應用運行期間)。Concurrent refinement threads調度是分層的。開始只有少許的線程被部署,最終添加取決於更新充滿緩衝區操做的數量。concurrent refinement threads的最大數量能夠由-XX:G1ConcRefinementThreads或者-XX:ParallelGCThreads控制。若是concurrent refinement threads的數量趕不上裝滿緩衝區的數量,而後mutator threads處理緩衝區過程-一般你應該努力去避免這中狀況。
OK,回到RSets-每個Region有一個RSets。RSets由三種級別的粒度-Sparse, Fine和Coarse。一個Per-Region-Table (PRT)是RSet存儲顆粒度級別一個抽象。sparse PRT是一個包含Card目錄的hash table。G1 GC內部維護這些card。card包含來自region的引用,這個region的引用是card到「owning region」的關聯的地址。fine-grain PRT是一個開放的hash table,每個entry表明一個指向owning region的引用的region。region裏面的card目錄,是一個bitmap。當達到fine-grain PRT的最大容量,coarse grain bitmap裏面的相應的coarse-grained bit被設置,相應地entry從 fine grain PRT刪除。coarse bitmap有一個每一個region對應的bit。coarse grain map設置bit意味着關聯的region包含到「owning region」的引用。
Collection Set (CSet)是一個gc期間即將被回收的region的set。對於 Young gc,CSet只包含Young Region,對於混合回收,CSets包含Young Region和Old Region。
若是CSets包含許多攜帶coarsened RSets的Region(注意,「coarsening of RSets」是根據RSets貫穿不一樣級別顆粒度的過渡期定義的),而後你會看到掃描RSets消耗時間的增加。GC階段這些掃描時間就是GC日誌裏面的「Scan RS (ms)」。若是RSets掃描時間至關於GC階段總時間很高,或者你的應用中它們表現很高,而後經過使用診斷選項-XX:+G1SummarizeRSetStats請觀察你的 Young GC 日誌輸出的「Did xyz coarsenings」(你能夠經過設置-XX:G1SummarizeRSetStatsPeriod=period指定週期頻率報告(GCs的數量))。
若是你回想起以前的文章,GC階段的「Update RS (ms)」展現了更新RSets花費時間,"Processed Buffers" 展現了GC期間更新緩衝區過程。若是你的日誌中發現這些問題,而後使用上述的選項去進一步深刻這些問題。
哪些選項一般能夠幫助肯定更新日誌緩衝區和concurrent refinement threads的問題。
-XX:+G1SummarizeRSetStats 設置成一-XX:G1SummarizeRSetStatsPeriod=1的輸出樣例:
上面的輸出展現了已經處理過的card和已完成的buffer的數量。它展現concurrent refinement threads作了100%的工做,mutator threads 什麼也沒有作(這是咱們說過的一個號的跡象!)。而後列出concurrent refinement thread的每個線程攝入到工做的時間。
上面褐色的部分展現了自從HotSpot VM啓動以來的累積狀態。累積狀態包括RSets的總和和最大RSets數量,已佔用Card的數量,最大Region尺寸信息。它一般展現自VM啓動以來任務完成的粗化總數。
此時此刻,介紹其餘選項是合適的-XX:G1RSetUpdatingPauseTimePercent=10。設置GC evacuation(疏散)階段期間G1 GC更新RSets消耗時間的百分比(默認是目標停頓時間的10%)。你能夠增大或減少百分比的值,以便在stop-the-world(STW)GC階段花費更多或更少的時間,讓concurrent refinement thread處理相應的緩衝區。
記住,減小百分比的值,你在推遲concurrent refinement thread的工做;所以,你會看到併發任務增長。
Reference Processing
evacuation階段期間和標記期間(併發標記多階段的一部分)G1 GC處理引用。
evacuation階段期間,掃描對象,複製,被處理後的時候找到引用對象。GC log裏面,引用處理(Ref proc)時間和叫作「Other」下面的一組連續工做:
Note:無用的引用被添加進pending list,GC log裏面展現的時間是reference enqueing time (Ref Enq)。
Remark階段,發生在併發標記階段以前。(note:多階段併發標記週期的一部分。請參考上篇文章的更多細節。)remark階段處理髮現的引用過程。GC log裏,你能夠在GC remark部分看到reference processing (GC ref-proc)時間:
若是你看到引用處理期間時間很長,經過受權命令行選項 -XX:+ParallelRefProcEnabled打開並行引用處理。
Evacuation Failure
若是你在GC日誌發現"evacuation failure", "to-space exhausted", "to-space overflow", "promotion failure"之類的字眼。這些術語的概念在G1 GC是類似的,請參考同一個。當沒有更多的空閒region提高到Old代,或者複製到survivor空間,heap因爲已經在最大值上而沒法擴展,evacuation Failure聚會發生:
若是對象被成功複製,G1須要更新對象引用,region必須是tenured。
若是對象複製失敗,G1會本身處理它們,在合適時機把region設置爲tenured。
所以在G1 log 發生evacuation failure你應該怎麼作:
找出調整的一些影響致使失敗-獲取heap最大和最小的基線和真實的停頓時間目標:移除任何heap大小的設置例如-Xmn, -XX:NewSize, -XX:MaxNewSize, -XX:SurvivorRatio等等。只使用-Xms, -Xmx,停頓時間目標-XX:MaxGCPauseMillis。
若是問題在基線運行依然存在,大對象(看下面的章節)分配沒有問題-矯正問題的方案是若是能夠的話增長heap區大小。
若是增長heap區大小行不通,若是你注意到爲了G1 GC能夠回收Old代標記階段不會提前執行,而後你刪掉-XX:MaxGCPauseMillis。這個選項默認佔用你heap區的45%。刪除這個選項能夠幫助提前開始標記階段。相反的,若是標記階段提前開始並無回收到不少空間,你應該在threshold默認值上增長來確保你的應用的存活數據能夠適應。
若是併發標記階段準時啓動,可是花了很長時間去完成;所以形成mixed gc週期延遲,最後致使evacuation失敗,而後old代沒有及時回收;使用選項-XX:ConcGCThreads增長併發標記線程數量。
若是「to-space」Survivor區域有問題, 增長-XX:G1ReservePercent。默認是java heap的10%。G1 GC設置錯誤上限預留內存,以防萬一"to-space"須要更多的空間。固然G1 GC只使用空間的50%,所以若是咱們不想應用使用大的Young能夠設置一個更大的值。
爲了幫助解釋evacuation failure的緣由,我想介紹一個用戶的選項:-XX:+PrintAdaptiveSizePolicy。這個選項會提供不少方法去阻止-XX:+PrintGCDetails選項。
讓我看一個-XX:+PrintAdaptiveSizePolicy可用時的片斷:
6062.121: [GC pause (G1 Evacuation Pause) (mixed) 6062.121: [G1Ergonomics (CSet Construction) start choosing CSet, _pending_cards: 129059, predicted base time: 52.34 ms, remaining time: 147.66 ms, target pause time: 200.00 ms] 6062.121: [G1Ergonomics (CSet Construction) add young regions to CSet, eden: 912 regions, survivors: 112 regions, predicted young region time: 256.16 ms] 6062.122: [G1Ergonomics (CSet Construction) finish adding old regions to CSet, reason: old CSet region num reached min, old: 149 regions, min: 149 regions]6062.122: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 912 regions, survivors: 112 regions, old: 149 regions, predicted pause time: 344.87 ms, target pause time: 200.00 ms] 6062.281: [G1Ergonomics (Heap Sizing) attempt heap expansion, reason: region allocation request failed, allocation request: 2097152 bytes] 6062.281: [G1Ergonomics (Heap Sizing) expand the heap, requested expansion amount: 2097152 bytes, attempted expansion amount: 4194304 bytes] 6062.281: [G1Ergonomics (Heap Sizing) did not expand the heap, reason: heap expansion operation failed] 6062.902: [G1Ergonomics (Heap Sizing) attempt heap expansion, reason: recent GC overhead higher than threshold after GC, recent GC overhead: 20.30 %, threshold: 10.00 %, uncommitted: 0 bytes, calculated expansion amount: 0 bytes (20.00 %)] 6062.902: [G1Ergonomics (Concurrent Cycles) do not request concurrent cycle initiation, reason: still doing mixed collections, occupancy: 9596567552 bytes, allocation request: 0 bytes, threshold: 5798205810 bytes (45.00 %), source: end of GC] 6062.902: [G1Ergonomics (Mixed GCs) continue mixed GCs, reason: candidate old regions available, candidate old regions: 1038 regions, reclaimable: 2612574984 bytes (20.28 %), threshold: 10.00 %] (to-space exhausted), 0.7805160 secs]
上面的片斷提供了不少信息-首先,讓咱們用上面GC log使用的命令行選項 server -Xms12g -Xmx12g -XX:+UseG1GC- -XX:NewSize=4g -XX:MaxNewSize=5g展現一些精彩的東西。
加粗部分展現用戶限制region的範圍在4-5G之間,所以限制了G1 GC的適應能力。若是G1須要去掉限制設置更小的值,它作不到;若是G1 GC須要增長空間範圍,超過了給它分配的空間,它作不到!
這是evacuation結束時打印的heap明確信息:
[Eden: 3648.0M(3648.0M)->0.0B(3696.0M) Survivors: 448.0M->400.0M Heap: 11.3G(12.0G)->9537.9M(12.0G)]
階段以後,G1不得不維持4096M做爲最小範圍(-XX:NewSize=4g),因爲基於G1的計算器,3696M用於Eden區,400M用戶Survivor區。然而,heap區標記回收的數據已經達到9537.9M。所以,G1用完「to-space」。下面兩次 evacuation階段的結果是evacuation failure:
混合evacuation階段1:
[Eden: 2736.0M(3696.0M)->0.0B(4096.0M) Survivors: 400.0M->0.0B Heap: 12.0G(12.0G)->12.0G(12.0G)]
混合evacuation階段2:
[Eden: 0.0B(4096.0M)->0.0B(4096.0M) Survivors: 0.0B->0.0B Heap: 12.0G(12.0G)->12.0G(12.0G)]
最終觸發Full GC:
6086.564: [Full GC (Allocation Failure) 11G->3795M(12G), 15.0980440 secs] [Eden: 0.0B(4096.0M)->0.0B(4096.0M) Survivors: 0.0B->0.0B Heap: 12.0G(12.0G)->3795.2M(12.0G)]
Full GC 能夠經過減小範圍/young代縮小至默認的minimum(Java heap的5%)。你也許會說old代足夠容納3795M的live data set (LDS)。然而, LDS明確耦合於設置young代minimum(4G),壓縮7891M以上空間。所以設置threshold爲默認的heap的45%(也就是 5529M左右),標記階段提前觸發
,混合回收期間回收不多的空間。heap使用保持增加,其餘的標記階段已經開始,可是標記階段完成,踢開混合GC,已使用空間在11.3G(正如看到heap第一行的信息)。此次回收遭遇evacuation failure。所以,這個問題陷在 「starting marking cycle too early」上面。
Humongous Allocations
最後一個我想介紹的概念是,也許用戶建立許多 humongous objects (H-objs),G1 GC處理H-objs。
辣麼,咱們爲何須要不一樣途徑去分配H-objs?
若是對象佔用Region50%的區域或者更多那麼被斷定爲humongous。分配humongous連續的空間。你能夠想象一下,若是G1在Young代分配humongous,並且它們存活很長時間,而後它們須要一些必要並且昂貴(記住H-obj連續的region)的操做,複製這些H-obj導survivor空間,最終這些H-obj升遷到Old代。所以,爲了不上面的操做,H-obj直接分配在Old代,而後分類整理,映射做爲humongousregion。
經過在Old代直接分配H-obj,G1避免在任何evacuation階段包含它們,它們所以也不用移動。Full GC期間,壓縮存活的H-obj。Full GC以後,multi-phased concurrent marking期間的清除階段 死亡的H-obj被回收。換句話說,H-obj在清除階段被回收,或者說它們在full gc期間被回收。
分配H-obj以前,G1 GC由於分配會交叉執行檢查heap使用百分比,標記的threshold。若是容許,G1 GC而後初始化concurrent marking週期。因爲咱們想避免evacuation failures和可能多的Full GC,這樣的方式被執行。結果在沒有更過的可用region存活對象evacuations儘量早檢查以便給G1儘可能多的時間去完成併發週期。
對於G1 GC,基本前提是沒有太多H-obj和他們存活時間很長。然而,因爲G1 GC的region尺寸取決於你的heap的 minimum值,它的發生創建在分配的region的尺寸上,你的humongous能夠看上去"normal"分配。這會致使須要H-obj分配在Old代的region上,甚至會致使evacuation failure,由於G1趕不上這些humongous分配。
如今你也許在思考如何找到humongous 分配致使evacuation failures的方法。這裏,再一次-XX:+PrintAdaptiveSizePolicy會來到你的解決方案中。
你的GC日誌中,能夠看到相似下面的一些東西:
1361.680: [G1Ergonomics (Concurrent Cycles) request concurrent cycle initiation, reason: occupancy higher than threshold, occupancy: 1459617792 bytes, allocation request: 4194320 bytes, threshold: 1449551430 bytes (45.00 %), source: concurrent humongous allocation]
所以,你能夠看到一次併發週期被要求發生因爲humongous分配須要4194320 bytes。
這些信息是有用的,所以你不但被告知你的應用有多少humongous分配(無論它們多仍是少),並且還告訴你發配的大小。此外,若是你認爲過多humongous分配,你能作的就是增長G1的region尺寸做爲適合humongous的規則尺寸。所以,例如,分配尺寸僅僅在4M之上。因此,爲了讓此次分配能夠規則分配,咱們須要16M的region尺寸。因此,這裏推薦明確設置命令行選項:-XX:G1HeapRegionSize=16M。
Note:回想一下我上篇文章,G1 region 跨越1M-32M(2的指數),分配要求稍微超過4M。所以,8M的region的尺寸不足以免humongous分配。問咱們須要下一個2的指數,16MB。
ok。我認爲此次我已經講解了大部分重要問題和G1概念。再一次,謝謝閱讀!