G1 垃圾回收器簡單調優
G1: Garbage First 低延遲、服務側分代垃圾回收器。
詳細介紹參見:JVM之G1收集器,這裡不再贅述。
關於調優目標:延遲、吞吐量
一、延遲,單次的延遲
單次的延遲關係到服務的響應時延,比如,在要求介面響應不超過100ms的服務裡,單次的延遲目標必然不能超過100ms。
服務的響應時間目標,不應該是指100%時間的服務響應。服務不可能是100%可用的,通常,我們對於服務的響應延遲目標也不是100%可用時間內的。
實際應用中,我們可能會以99.9%時間內,延遲不超過100ms為目標。
對於G1,會有一些預設設定,以使應用者在不做任何調整的情況下,依然能高效的執行。
-XX:MaxGCPauseMillis=200:目標最大gc暫停時間,預設為200ms,這只是期望的目標延遲。我們知道G1有相應的收集演算法,會根據收集的資訊及檢測的垃圾量動態的調整年輕代與老年代的大小以盡力達到這個目標。
使用此配置需要注意的一點是,不要和 Xmn 年輕代同時設定,我們上面提到過,G1會為了最大gc暫停時間目標而動態的調整年輕代大小,因此,如果設定了 Xmn,那麼固定了年輕代的大小就會影響G1的智慧調整適應。
二、吞吐量,有多少總的延遲
總的延遲關係到服務的可用時間率、吞吐量,比如,100分鐘內總的gc延遲1分鐘,那麼服務的可用率就是99%。如果既定的目標是99.9%,那麼總的延遲就不能超過6秒鐘。
總的延遲=單次延遲*gc次數。
單次延遲我們在一.1中已經論述,那麼現在就需要通過降低gc次數來達到降低總延遲的目標,
gc觸發於應用記憶體佔用達到一定比例閾值,因此想要降低gc頻次,那麼就需要適當調大應用可使用堆大小:Xmx。
應用到底需要使用多大的應用記憶體,這個需要根據實際的需求確定,可以通過壓測,不斷的微調來找到最適合的Xmx設定,過大或者過小都會影響服務的服務能力。
三、gc日誌
配置輸出gc日誌:-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps。
下面是一段實際應用中的yong GC日誌:
1 [GC pause (G1 Evacuation Pause) (young), 0.1006389 secs] 2 [Parallel Time: 45.6 ms, GC Workers: 38] 3 [GC Worker Start (ms): Min: 4053175.2, Avg: 4053184.8, Max: 4053215.7, Diff: 40.5] 4 [Ext Root Scanning (ms): Min: 0.0, Avg: 1.2, Max: 8.6, Diff: 8.6, Sum: 47.1] 5 [Update RS (ms): Min: 0.0, Avg: 8.4, Max: 41.3, Diff: 41.3, Sum: 317.3] 6 [Processed Buffers: Min: 0, Avg: 13.3, Max: 39, Diff: 39, Sum: 505] 7 [Scan RS (ms): Min: 0.0, Avg: 0.2, Max: 0.4, Diff: 0.4, Sum: 7.7] 8 [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3] 9 [Object Copy (ms): Min: 0.0, Avg: 20.6, Max: 32.6, Diff: 32.5, Sum: 783.9] 10 [Termination (ms): Min: 0.0, Avg: 4.5, Max: 5.2, Diff: 5.2, Sum: 171.5] 11 [GC Worker Other (ms): Min: 0.0, Avg: 0.2, Max: 0.6, Diff: 0.6, Sum: 5.9] 12 [GC Worker Total (ms): Min: 4.1, Avg: 35.1, Max: 44.6, Diff: 40.5, Sum: 1333.8] 13 [GC Worker End (ms): Min: 4053219.7, Avg: 4053219.9, Max: 4053220.4, Diff: 0.8] 14 [Code Root Fixup: 0.3 ms] 15 [Code Root Purge: 0.0 ms] 16 [Clear CT: 1.5 ms] 17 [Other: 53.2 ms] 18 [Choose CSet: 0.0 ms] 19 [Ref Proc: 39.2 ms] 20 [Ref Enq: 8.6 ms] 21 [Redirty Cards: 1.3 ms] 22 [Humongous Reclaim: 0.0 ms] 23 [Free CSet: 2.0 ms] 24 [Eden: 4708.0M(4708.0M)->0.0B(4724.0M) Survivors: 204.0M->188.0M Heap: 5528.0M(8192.0M)->804.9M(8192.0M)] 25 [Times: user=1.37 sys=0.02, real=0.10 secs]
第一行:指明GC型別,一次GC的總耗時 0.1006389 secs,即100ms。
第二行:並行階段STW時間匯,GC工作執行緒數(配置:-XX:ParallelGCThreads。CPU數量小於8時,值取CPU個數,最大為8,CPU數量大於8時,值取(CPU個數*5/8))。
第三行:GC執行緒開始工作時間,Min最小值、Avg平均值、Max最大值、Diff偏移平均的值(Max-Min)
第四行:外部根區掃描,包括堆外區、JNI引用、JVM系統目錄、Classloaders等。Sum總耗時。
第五行:RSets(Remembered Sets )時間資訊更新,G1依據-XX:MaxGCPauseMillis引數來設定目標暫停時間,RSet更新的時間耗時應小於目標暫停時間的10%。可以通過修改配置 XX:G1RSetUpdatingPauseTimePercent 設預期定耗時佔用比。
第六行:已處理緩衝區:即在優化執行緒中處理dirty card分割槽掃描時記錄的日誌緩衝區。
第七行:RSets掃描。
第八行:程式碼Root掃描,經過JIT編譯後的程式碼裡引用了heap中的物件,引用關係儲存在RSet中。
第九行:拷貝存活物件到新的Region耗時。
第十行:GC執行緒完成任務之後嘗試結束到真正結束的耗時。GC執行緒結束前會檢查其它執行緒是否有未完成的任務,如果有則會協助完成之後再結束。
第十一行:執行緒花費在其他工作上的時間,
第十二行:並行階段的GC時間總和,包含GC以及GC Worker Other時間(47.1+317.3+7.7+0.3+783.9+171.5+5.9)。
第十三行:GC執行緒結束時間,Min最小值、Avg平均值、Max最大值、Diff偏移平均的值(Max-Min)
第十四行:修復GC期間code root指標改變的耗時。
第十五行:清除code root耗時,root中已經失效,不再指向Region中物件的引用。
第十六行:清除card tables 中的dirty card的耗時。
第十七行:其它GC活動耗時。
第十八行:選擇要進行回收的分割槽放入CSet(G1選擇的標準是垃圾最多的分割槽優先,也就是存活物件率最低的分割槽優先)
第十九行:處理各種引用——soft、weak、final、phantom、JNI等。
第二十行:遍歷所有的引用,將不能回收的放入pending列表。
第二十一行:在回收過程中被修改的card將會被重置為dirty。
第二十二行:JDK8特性,巨型物件可以在新生代收集的時候被回收,可以通過G1ReclaimDeadHumongousObjectsAtYoungGC進行配置,預設為true。
第二十三行:釋放CSet,將要釋放的分割槽還回到free列表。
第二十四行:年輕代回收狀態,Eden區滿,執行回收,回收後佔用為0,且Eden區大小重新調整(G1根據預測演算法動態調整);Survivors變小說明有提升;Heap收集前記憶體佔用及最大值,GC收集後記憶體佔用及最大值。最大值由Xmx配置,保持不變。
第二十五行:user:垃圾收集執行緒在新生代垃圾收集過程中消耗的CPU時間,這個時間跟垃圾收集執行緒的個數有關,可能會比real time大很多;sys:核心態執行緒消耗的CPU時間;real:本次垃圾收集真正消耗的時間。
四、分析工具
1、個人推薦gcviewer,圖形化展示各個收集指標,另附Summary、Memory及Pause等明細統計,可以檢視gc次數,總的GC耗時,最大、最小耗時、吞吐量等等:
2、線上工具:https://gceasy.io/,可以上傳GC日誌生成GC報告,下圖為報告中的關於GC耗時分佈統計:
&n