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耗時分布統計: