原文出處:Minor GC vs Major GC vs Full GC
在Plumbr的工做過程當中遇到GC間隙功能探測問題使我不得不關注相關文章,書籍,簡報。自始至終,我不止一次迷惑於 Minor, Major and Full GC 的用法。爲了搞清楚這些疑惑我寫這篇博客。html
這篇博客指望讀者瞭解JVM 底層 GC機制。jvm heap區 分爲 Eden,Survivor,Tenured/Old區。分代概念以及不一樣的GC算法超出了了這次討論的範圍。java
Minor GC算法
新生代(由 Eden and Survivor 組成)的垃圾收集叫作Minor GC。該定義清晰易於理解。可是如下幾點仍然須要咱們注意:併發
當jvm 沒法爲新建對象分配內存空間的時候Minor GC被觸發,例如新生代空間被佔滿。所以新生代空間佔用率越高,Minor GC越頻繁。oracle
當空間被佔滿,它下面的全部對象都會被複制,並且堆頂指針從空閒空間的零位置移動(譯者注:此處爲複製算法)。所以取代傳統的標記清除壓縮算法,去清理Eden區和Survivor區,所以Eden和Survivor區無內存碎片產生。jvm
在Minor GC期間,實際上Tenured區被忽略,實際上Tenured區引用young區的對象被看成GC roots。在標記期間young區引用的Tenured區對象的對象會被忽略。ide
反對全部Minor GC都會觸發「stop-the-world」這一觀點。在大多數應用中,忽略"stop-the-world"停留時長。不能否認的是新生代中的一些對象被錯誤當成垃圾而不會被移動到Survivor/Old區。若是筆者反對的觀點成立,一些新生對象因爲不合適被看成垃圾,致使Minor GC停頓將會耗費更多的時間。工具
所以Minor GC的狀況至關清楚了,每次Minor GC只清理新生代。優化
尋找減小GC停頓時長的方式?automatically detect what causes GC pauses 能夠解決你的難題。ui
Major GC vs Full GC
在目前的項目中尚未明確的定義,這點須要注意。JVM規範和垃圾收集研究論文都沒有說起,可是乍一看,這些創建在咱們掌握了Minor GC清理新生代上的定義並不是難事:
Major GC清理Tenured區。
Full GC清理整個heap區,包括Yong區和Tenured區。
不幸的是這些有點複雜,難於解釋。首先,Minor GC觸發Major GC,在不少情形下,將這二者分開是不可能的。另外一方面,許多現代垃圾收集平臺傾向於清理Tenured區,所以,用「cleaning」術語僅僅是部分正確。
GC不管被稱做Major GC仍是Full GC,你應該搞清楚不管GC中止全部的應用線程仍是它能夠和應用線程同時進行。
這個問題甚至發生在JVM標準工具。下面是最好的解決渠道和實例,讓咱們比較運行在同一個JVM的兩個不一樣工具對Concurrent Mark and Sweep收集器的輸出(-XX:+UseConcMarkSweepGC)。
首先嚐試使用 jstat :
Time S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 5.7 34048.0 34048.0 0.0 34048.0 272640.0 194699.7 1756416.0 181419.9 18304.0 17865.1 2688.0 2497.6 3 0.275 0 0.000 0.275 6.7 34048.0 34048.0 34048.0 0.0 272640.0 247555.4 1756416.0 263447.9 18816.0 18123.3 2688.0 2523.1 4 0.359 0 0.000 0.359 7.7 34048.0 34048.0 0.0 34048.0 272640.0 257729.3 1756416.0 345109.8 19072.0 18396.6 2688.0 2550.3 5 0.451 0 0.000 0.451 8.7 34048.0 34048.0 34048.0 34048.0 272640.0 272640.0 1756416.0 444982.5 19456.0 18681.3 2816.0 2575.8 7 0.550 0 0.000 0.550 9.7 34048.0 34048.0 34046.7 0.0 272640.0 16777.0 1756416.0 587906.3 20096.0 19235.1 2944.0 2631.8 8 0.720 0 0.000 0.720 10.7 34048.0 34048.0 0.0 34046.2 272640.0 80171.6 1756416.0 664913.4 20352.0 19495.9 2944.0 2657.4 9 0.810 0 0.000 0.810 11.7 34048.0 34048.0 34048.0 0.0 272640.0 129480.8 1756416.0 745100.2 20608.0 19704.5 2944.0 2678.4 10 0.896 0 0.000 0.896 12.7 34048.0 34048.0 0.0 34046.6 272640.0 164070.7 1756416.0 822073.7 20992.0 19937.1 3072.0 2702.8 11 0.978 0 0.000 0.978 13.7 34048.0 34048.0 34048.0 0.0 272640.0 211949.9 1756416.0 897364.4 21248.0 20179.6 3072.0 2728.1 12 1.087 1 0.004 1.091 14.7 34048.0 34048.0 0.0 34047.1 272640.0 245801.5 1756416.0 597362.6 21504.0 20390.6 3072.0 2750.3 13 1.183 2 0.050 1.233 15.7 34048.0 34048.0 0.0 34048.0 272640.0 21474.1 1756416.0 757347.0 22012.0 20792.0 3200.0 2791.0 15 1.336 2 0.050 1.386 16.7 34048.0 34048.0 34047.0 0.0 272640.0 48378.0 1756416.0 838594.4 22268.0 21003.5 3200.0 2813.2 16 1.433 2 0.050 1.484
上面片斷取自JVM啓動17秒,以這些信息爲基礎,咱們能夠推斷2次Full GC以前進行12次 Minor GC,一共耗費50毫秒。你能夠經過一些基於GUI的工具證明,例如jconsole和jvisualvm。
在下結論以前,咱們看一下JVM運行garbage collection logs。顯然-XX:+PrintGCDetails 告訴咱們更多細節:
java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC eu.plumbr.demo.GarbageProducer
3.157: [GC (Allocation Failure) 3.157: [ParNew: 272640K->34048K(306688K), 0.0844702 secs] 272640K->69574K(2063104K), 0.0845560 secs] [Times: user=0.23 sys=0.03, real=0.09 secs] 4.092: [GC (Allocation Failure) 4.092: [ParNew: 306688K->34048K(306688K), 0.1013723 secs] 342214K->136584K(2063104K), 0.1014307 secs] [Times: user=0.25 sys=0.05, real=0.10 secs] ... cut for brevity ... 11.292: [GC (Allocation Failure) 11.292: [ParNew: 306686K->34048K(306688K), 0.0857219 secs] 971599K->779148K(2063104K), 0.0857875 secs] [Times: user=0.26 sys=0.04, real=0.09 secs] 12.140: [GC (Allocation Failure) 12.140: [ParNew: 306688K->34046K(306688K), 0.0821774 secs] 1051788K->856120K(2063104K), 0.0822400 secs] [Times: user=0.25 sys=0.03, real=0.08 secs] 12.989: [GC (Allocation Failure) 12.989: [ParNew: 306686K->34048K(306688K), 0.1086667 secs] 1128760K->931412K(2063104K), 0.1087416 secs] [Times: user=0.24 sys=0.04, real=0.11 secs] 13.098: [GC (CMS Initial Mark) [1 CMS-initial-mark: 897364K(1756416K)] 936667K(2063104K), 0.0041705 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 13.102: [CMS-concurrent-mark-start] 13.341: [CMS-concurrent-mark: 0.238/0.238 secs] [Times: user=0.36 sys=0.01, real=0.24 secs] 13.341: [CMS-concurrent-preclean-start] 13.350: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 13.350: [CMS-concurrent-abortable-preclean-start] 13.878: [GC (Allocation Failure) 13.878: [ParNew: 306688K->34047K(306688K), 0.0960456 secs] 1204052K->1010638K(2063104K), 0.0961542 secs] [Times: user=0.29 sys=0.04, real=0.09 secs] 14.366: [CMS-concurrent-abortable-preclean: 0.917/1.016 secs] [Times: user=2.22 sys=0.07, real=1.01 secs] 14.366: [GC (CMS Final Remark) [YG occupancy: 182593 K (306688 K)]14.366: [Rescan (parallel) , 0.0291598 secs]14.395: [weak refs processing, 0.0000232 secs]14.395: [class unloading, 0.0117661 secs]14.407: [scrub symbol table, 0.0015323 secs]14.409: [scrub string table, 0.0003221 secs][1 CMS-remark: 976591K(1756416K)] 1159184K(2063104K), 0.0462010 secs] [Times: user=0.14 sys=0.00, real=0.05 secs] 14.412: [CMS-concurrent-sweep-start] 14.633: [CMS-concurrent-sweep: 0.221/0.221 secs] [Times: user=0.37 sys=0.00, real=0.22 secs] 14.633: [CMS-concurrent-reset-start] 14.636: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
基於這些信息,咱們看到12次Minor GC 以後一些事情開始發生,可是與上面2次Full GC不一樣的是老年代的不一樣階段只有一次GC:
初始化標記階段,耗費0.0041705 seconds大約4ms。爲了初始化標記這個階段進行「stop-the-world」。
標記和預清除併發階段,和應用先線程並行執行。
最後重複標記階段,耗費0.0462010 seconds大約46ms。再次進行「stop-the-world」。
併發執行清除階段,正如名稱所示,不執行「stop-the-world」,併發實施操做。
正如咱們們從GC日誌看到的真實狀況,事實上,替代兩次Full GC的僅僅一次Major GC清理Old space。
若是你考慮了基於jstat展現的數據的狀況,你會作出正確結論。它正確展現出兩次由於全部活動線程而進行兩次「stop-the-world」,總共耗費50ms。若是你想爲了吞吐量嘗試優化,那麼你會被誤導,僅僅在初始化標記和最後重複標記階段而進行「stop-the-world」,the jstat輸出徹底隱藏了併發工做。
結論
綜合狀況來看,這是避免考慮項目中Minor, Major or Full GC的最好方式。反之,監控你的應用延遲或者吞吐量,將結果和GC事件聯繫起來。連同這些事件一塊兒,你額外須要相關信息,特別是GC事件強迫中止應用線程和併發處理部分事件。
若是你對本次內容感興趣,這是目前有效的實例章節Garbage Collection Handbook。