摘要:注意到通過并發更新它們來幫助維護通常在應用運行期間。累積狀態包括的總和和最大數量,已占用的數量,最大尺寸信息。階段期間和標記期間并發標記多階段的一部分處理引用。之后,期間的清除階段死亡的被回收。
原文出處:Tips for Tuning the Garbage First Garbage Collector
這是由兩部分組成的系列的第二篇關于G1垃圾回收器的文章,你可以在2013.07.17的InfoQ上找到第一部分:G1: One Garbage Collector To Rule Them All。
在我我們了解如何調整G1 GC之前,首先我必須了解G1定義的關鍵概念。在這篇文章里,我會首先介紹概念,然后討論如何調整G1(適當的時候)。
Remembered Sets
從之前的文章回憶起它: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”。
這里有兩個概念幫助理解RSets:
Post-write barriers
Concurrent refinement threads
屏障代碼在寫操作之后(因此名稱是“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概念。再一次,謝謝閱讀!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66367.html
摘要:之前根據的內存管理白皮書介紹了在分代算法中的幾個垃圾收集器,本文將介紹垃圾收集器。本節介紹的收集過程,收集器主要包括了以下種操作年輕代收集并發收集,和應用線程同時執行混合式垃圾收集必要時的接下來,我們進行一一介紹。 之前根據 Sun 的內存管理白皮書介紹了在 HotSpot JVM 分代算法中的幾個垃圾收集器,本文將介紹 G1 垃圾收集器。 G1 的主要關注點在于達到可控的停頓時間,在...
摘要:本文將會深入分析的引擎的內部實現。該引擎使用在谷歌瀏覽器內部。同其他現代引擎如或所做的一樣,通過實現即時編譯器在執行時將代碼編譯成機器代碼。這可使正常執行期間只發生相當短的暫停。 原文 How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code 幾周前我們開始了一個系列博文旨在深入...
摘要:表示允許垃圾收集線程處理本次垃圾收集開始前沒有處理好的日志緩沖區,這可以確保當前分區的是最新的。垃圾收集線程在完成其他任務的時間展示每個垃圾收集線程的最小最大平均差值和總共時間。 本文翻譯自:https://www.redhat.com/en/blog/collecting-and-reading-g1-garbage-collector-logs-part-2?source=auth...
閱讀 2672·2019-08-30 15:55
閱讀 1804·2019-08-30 15:53
閱讀 2656·2019-08-29 18:38
閱讀 928·2019-08-26 13:49
閱讀 502·2019-08-23 15:42
閱讀 3114·2019-08-22 16:33
閱讀 1003·2019-08-21 17:59
閱讀 1082·2019-08-21 17:11