G1调优分析
目錄
1、暢想GC的目標
2、jvm調優的目標
3、GC調優時機
4、垃圾收集器的選擇
5、G1調優策略
6、G1垃圾收集實踐
6.1、JVM自動選擇垃圾收集器
6.2、G1垃圾收集
6.3、GC日志分析
7、小結
前言 c++和java之間有一堵由內存動態分配和垃圾收集技術所圍成的墻,墻外面的人想進去墻里面的人想出來。
1、暢想GC的目標
? ? 詹姆斯·高斯林?(James Gosling)是一名軟件專家,喊出了口號:“一次編寫,到處亂跑。” 他在1995年寫java這門編程語言的時候,可能并沒有想到java會如此廣泛的應用于web開發,沒有意識到要進行更多的web交互場景,應用對停頓時間要求是如此的嚴格,否則在剛開始設計垃圾回收的時候就不會粗暴的直接將應用線程停掉了。這個在現在來看是不太能接受的,隨著jdk往上發展,web的高并發,交互場景的越來越頻繁,所以要追求低停頓和高吞吐量成了程序員們的追求,所以垃圾收集器就需要與時俱進的進行不斷的優化,再優化,直到沒有停頓。 以至于出現了Z-GC,Z也不知道是不是zero的意思,代表著程序員們的極致追求,沒有停頓時間。 The Z Garbage Collector (ZGC) is a scalable low latency garbage collector. ZGC performs all expensive work concurrently, without stopping the execution of application threads for more than 10ms, which makes is suitable for applications which require low latency and/or use a very large heap (multi-terabytes). The Z Garbage Collector is available as an experimental feature, and is enabled with the command-line options-XX:+UnlockExperimentalVMOptions -XX:+UseZGC.ZGC的官網描述:Z-GC目標:能夠讓應用gc停頓的時間低于:10ms,適用于更大堆。
參考資料:Z-Gabage:https://docs.oracle.com/en/java/javase/11/gctuning/z-garbage-collector1.html#GUID-A5A42691-095E-47BA-B6DC-FB4E5FAA43D02、jvm調優的目標
隨著互聯網的web應用流量激增,堆內存空間的不斷增大,從官方垃圾收集器的一步步優化之路不難發現,程序員對JVM 的垃圾收集追求的目標在于以下三點:- 吞吐量-Throughput;運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)
- 停頓時間-PauseTime;垃圾收集器進行 垃圾回收中斷應用執行響應的時間
- GC的頻率-GCTimes;一般不做硬性要求,能接受一定程度的younggc,但一定要避免full-gc;
3、GC調優時機
什么時候才需要調優?GC到底影響什么?GC的常見癥狀? 首先一定不是無聊,天馬行空的改參數,那樣反而適得其反。 例如GC停頓導致常見的問題的癥狀:- 系統CPU飆升很快;
- 系統運行的響應時間長,接口響應超時;
- 網站經常不定期出現:長時間沒有響應的現象。
- gc次數太多,用戶線程代碼執行受影響,cpu使用會高?
- 內存的使用率逐漸增大,不夠用了;
4、垃圾收集器的選擇
jvm調優:如何調優才能實現我們的目標呢,首先是垃圾收集器的選擇。 首先要明確,jvm的調優沒有萬能公式,每個項目背景和要求不同,調優的策略和參數都不一樣。 首先關于垃圾收集器的選擇:并不是并發度越高就越好的,停頓時間越短就越好。需要根據具體的情況來看。 官網垃圾收集器的選擇標準:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref28 Selecting a Collector 首先讓jvm來自動選擇,不能滿足; 調整堆的大小,減少垃圾回收次數; Unless your application has rather strict pause time requirements, first run your application and allow the VM to select a collector. If necessary, adjust the heap size to improve performance.? 如果仍然不能滿足: If the performance still does not meet your goals, then use the following guidelines as a starting point for selecting a collector.內存小于100m,可以選擇serial收集器; * If the application has a small data set (up to approximately 100 MB), then select the serial collector with the option? -XX:+UseSerialGC.單線程,使用serial; * If the application will be run on a single processor and there are no pause time requirements, then let the VM select the collector, or select the serial collector with the option-XX:+UseSerialGC.沒有停頓時間的要求,關注吞吐量,選擇并行收集器; * If (a) peak application performance is the first priority and (b) there are no pause time requirements or pauses of 1 second or longer are acceptable, then let the VM select the collector, or select the parallel collector with-XX:+UseParallelGC.關注停頓時間的要求,可以選擇G1; * If response time is more important than overall throughput and garbage collection pauses must be kept shorter than approximately 1 second, then select the concurrent collector with -XX:+UseConcMarkSweepGC or-XX:+UseG1GC.根據官網推薦,垃圾收集器的選擇標準總結如下:
- 比如cpu的核心數:如果是單核cpu,選擇并發垃圾收集器也沒有用,因為單核還是串行的,線程的切換反而降低了垃圾收集的效率;
- 堆的大小:類似G1垃圾收集,它的內存布局讓它更適合大堆內存的收集,而小堆內存串行和CMS就能有比較高的性能;
- 是否關注停頓時間:如果不關注停頓時間關注吞吐量,串行和CMS就能提供很好的性能,也并不是G1就是最好的;
5、G1調優策略
官方也給出了G1調優的一些建議指南:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations- (1)不要手動設置新生代和老年代的大小,只要設置整個堆的大小
- G1收集器在運行過程中,會自己調整新生代和老年代的大小 其實是通過自動調整young代的大小來調整對象晉升的速度,從而達到為收集器設置的暫停時間目標,如果手動設置了大小就意味著放棄了G1的自動調優,破壞了停頓時間策略;
- (2) 不斷調優暫停時間;不要太嚴格
- 一般情況下這個值設置到100ms或者200ms都是可以的(不同情況下會不一樣),但如果設置成50ms就不太合理。
- 暫停時間設置的太短,就會導致出現G1跟不上垃圾產生的速度。最終退化成Full GC。
- 所以對這個參數的調優是一個持續 的過程,逐步調整到最佳狀態。暫停時間只是一個目標,并不能總是得到滿足。
- (3)使用-XX:ConcGCThreads=n來增加標記線程的數量
- IHOP如果閥值設置過高,可能會遇到轉移失敗的風險,比如對象進行轉移時空間不足。如果閥值設置過低,就會使標 記周期運行過于頻繁,并且有可能混合收集期回收不到空間。 IHOP值如果設置合理,但是在并發周期時間過長時,可以嘗試增加并發線程數,調高ConcGCThreads。
- (4)MixedGC調優
- -XX:InitiatingHeapOccupancyPercent=45?:觸發并發標記的堆內存使用占比;
- -XX:G1MixedGCLiveThresholdPercent
- -XX:G1MixedGCCountTarger
- -XX:G1OldCSetRegionThresholdPercent
- (5)條件允許的情況下,適當增加堆內存大小
6、G1垃圾收集實踐
一般如果發現gc頻繁,或者gc停頓時間長不可接受,我們就需要對gc的參數進行調整,然后通過日志,調整參數,達到一個GC停頓時間和吞吐量的最佳的狀態,我們將用以下代碼來模擬查看堆區的gc日志來進一步了解jvm各垃圾收集的工作過程,由于CMS之前分析過,這里不在贅述,主要分析下G1收集器。6.1、JVM自動選擇垃圾收集器
首先我們自己不設置垃圾收集器,讓JVM自己來為我們選擇,因為官方推薦這么做,當不知道如何選擇的時候可以把這個權限交給JVM,JVM會默認的選擇一個垃圾回收器,用下面一小段代碼來不斷產生垃圾,看垃圾收集器的作用及日志。 public class HeapOomGCTest {public static String heapOOMtest() throws InterruptedException {List<Person> list = new ArrayList<Person>();while (true) {list.add(new Person());System.out.println("add Person success~");Thread.sleep(10);}}public static void main(String[] args) throws InterruptedException {heapOOMtest();} }設置參數如下:
-Xms30m -Xmx30m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:JvmAutoSelectGC.log JvmAutoSelectGC.log日志解讀: Java HotSpot(TM) 64-Bit Server VM (25.201-b09) for bsd-amd64 JRE (1.8.0_201-b09), built on Dec 15 2018 18:35:23 by "java_re" with gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00) Memory: 4k page, physical 16777216k(651656k free)/proc/meminfo:CommandLine flags: -XX:InitialHeapSize=31457280 -XX:MaxHeapSize=31457280 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC2020-11-22T09:05:50.054-0800: 10.188: [GC (Allocation Failure) [PSYoungGen: 8192K->1008K(9216K)] 8192K->1276K(29696K),0.0019565 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]日志具體內容分析:
-XX:+UseParallelGC :通過日志發現在沒有設置GC垃圾收集器的情況下,JDK1.8默認使用的垃圾收集器:ParallerGC = ParallerScavge +?ParallerOld GC:表明進行了一次垃圾回收,前面沒有Full修飾,PSYoungGen表明這是一次新生代的Minor GC,這里不管是新生代還是老年代都會STW。 Allocation Failure:表明本次引起GC的原因是因為在年輕代中沒有足夠的空間能夠存儲新的數據了。 8192K->1008K(9216K):(單位是KB)三個參數分別為:GC前該內存區域(這里是年輕代)使用容量,GC后該內存區域使用容量,該內存區域總容量。 因為我設置的總堆大小為30M=30720kb,-XX:NewRatio=2,出去其他的內存占用,所以新生代Eden區的總容量為:9216kb 0.0019565 secs:該內存區域GC耗時,單位是秒 8192K->1276K(29696K):三個參數分別為:堆區垃圾回收前的大小,堆區垃圾回收后的大小,堆區總大小。 [Times: user=0.00 sys=0.01, real=0.00 secs]:分別表示用戶態耗時,內核態耗時和總耗時6.2、G1垃圾收集
參數設置如下: -Xms500m -Xmx500m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+UseG1GC -XX:MaxGCPauseMillis=15 -Xloggc:G1-gc.log CommandLine flags: -XX:CMSInitiatingOccupancyFraction=30 -XX:InitialHeapSize=5242880 -XX:MaxHeapSize=5242880 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC #使用 G1垃圾收集; # 什么時候發生的GC,相對的時間刻,GC發生的區域young,總共花費的時間,0.00478s, 2020-11-22T13:45:42.218-0800: 0.196: [GC pause (G1 Evacuation Pause) (young), 0.0018535 secs]# 表示8個垃圾回收線程,并行的時間[Parallel Time: 1.2 ms, GC Workers: 8]# GC線程開始相對于上面的0.196的時間刻[GC Worker Start (ms): Min: 122.3, Avg: 122.5, Max: 123.0, Diff: 0.7][Ext Root Scanning (ms): Min: 0.0, Avg: 0.2, Max: 0.9, Diff: 0.9, Sum: 1.9][Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0][Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Object Copy (ms): Min: 0.0, Avg: 0.2, Max: 0.4, Diff: 0.4, Sum: 1.8][Termination (ms): Min: 0.0, Avg: 0.4, Max: 0.5, Diff: 0.5, Sum: 3.0][Termination Attempts: Min: 1, Avg: 1.8, Max: 4, Diff: 3, Sum: 14][GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1][GC Worker Total (ms): Min: 0.3, Avg: 0.8, Max: 1.1, Diff: 0.8, Sum: 6.8][GC Worker End (ms): Min: 123.3, Avg: 123.4, Max: 123.5, Diff: 0.1][Code Root Fixup: 0.0 ms][Code Root Purge: 0.0 ms][Clear CT: 0.1 ms][Other: 0.5 ms][Choose CSet: 0.0 ms][Ref Proc: 0.3 ms][Ref Enq: 0.0 ms][Redirty Cards: 0.2 ms][Humongous Register: 0.0 ms][Humongous Reclaim: 0.0 ms][Free CSet: 0.0 ms][Eden: 1024.0K(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->1024.0K Heap: 1024.0K(6144.0K)->536.1K(6144.0K)][Times: user=0.01 sys=0.00, real=0.00 secs] G1-GC.log部分日志截取如下:從上面的日志來看,其日志格式復雜了很多,可以參考G1日志詳細解讀:https://blogs.oracle.com/poonam/understanding-g1-gc-logs
6.3、GC日志分析
6.3.1、本地工具查看:gc-viewer 使用命令運行: java -jar?gcviewer-1.36-SNAPSHOT.jar ?運行這個工具jar包,打開我們剛剛生成的G1-GC.log文件,我們就可以看見jvm調優的幾個核心指標,其實從工具我們也可以清楚的看到jvm的gc調優關注的是什么,調的是什么,停頓時間-吞吐量-GC頻率。
?? 可以發現,通過此工具清楚的看到 g1發生gc的詳情,停頓耗時,gc次數,吞吐量都一目了然,之前我們的設置參數如下,設置的比較小,這樣效果比較明顯。 -Xms5m -Xmx5m -XX:+PrintGCDetails -XX:CMSInitiatingOccupancyFraction=30???-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:MaxGCPauseMillis=5 -XX:+UseG1GC??-Xloggc:G1-GC.log我們發現其核心指標如下:
| 吞吐量 | 最小停頓時間 | 平均停頓時間 | 最大停頓時間 | gc次數 |
| 97.25 | 0.00048 | 0.00254 | 0.00671 | 13 |
7、小結
GC的垃圾收集器的參數和選擇都不唯一,需要根據項目的場景及硬件條件作出選擇,適合的就是最好的,沒有銀彈。- gc調優核心指標
- 吞吐量 —?throughtput
- 停頓時間?—pause time
- gc頻率?— gc times
- GC日志查看工具:
- GCeasy:在線工具;
- GCviewer:本地工具;java -jar gc-viewer.jar
總結
- 上一篇: LED驱动器 DC恒流电源板模块
- 下一篇: 冒泡型事件