摘要:分代概念以及不同的算法超出了了此次討論的范圍。在標記期間區(qū)引用的區(qū)對象的對象會被忽略。不可否認的是新生代中的一些對象被錯誤當成垃圾而不會被移動到區(qū)。結論綜合情況來看,這是避免考慮項目中的最好方式。
原文出處:Minor GC vs Major GC vs Full GC
在Plumbr的工作過程中遇到GC間隙功能探測問題使我不得不關注相關文章,書籍,簡報。自始至終,我不止一次迷惑于 Minor, Major and Full GC 的用法。為了搞清楚這些疑惑我寫這篇博客。
這篇博客期望讀者了解JVM 底層 GC機制。jvm heap區(qū) 分為 Eden,Survivor,Tenured/Old區(qū)。分代概念以及不同的GC算法超出了了此次討論的范圍。
Minor GC
新生代(由 Eden and Survivor 組成)的垃圾收集叫做Minor GC。該定義清晰易于理解。但是以下幾點仍然需要我們注意:
當jvm 無法為新建對象分配內(nèi)存空間的時候Minor GC被觸發(fā),例如新生代空間被占滿。因此新生代空間占用率越高,Minor GC越頻繁。
當空間被占滿,它下面的所有對象都會被復制,而且堆頂指針從空閑空間的零位置移動(譯者注:此處為復制算法)。因此取代傳統(tǒng)的標記清除壓縮算法,去清理Eden區(qū)和Survivor區(qū),因此Eden和Survivor區(qū)無內(nèi)存碎片產(chǎn)生。
在Minor GC期間,實際上Tenured區(qū)被忽略,實際上Tenured區(qū)引用young區(qū)的對象被當作GC roots。在標記期間young區(qū)引用的Tenured區(qū)對象的對象會被忽略。
反對所有Minor GC都會觸發(fā)“stop-the-world”這一觀點。在大多數(shù)應用中,忽略"stop-the-world"停留時長。不可否認的是新生代中的一些對象被錯誤當成垃圾而不會被移動到Survivor/Old區(qū)。如果筆者反對的觀點成立,一些新生對象由于不合適被當作垃圾,導致Minor GC停頓將會耗費更多的時間。
因此Minor GC的情況相當清楚了,每次Minor GC只清理新生代。
尋找減少GC停頓時長的方式?automatically detect what causes GC pauses 可以解決你的難題。
Major GC vs Full GC
在目前的項目中還沒有明確的定義,這點需要注意。JVM規(guī)范和垃圾收集研究論文都沒有提及,但是乍一看,這些建立在我們掌握了Minor GC清理新生代上的定義并非難事:
Major GC清理Tenured區(qū)。
Full GC清理整個heap區(qū),包括Yong區(qū)和Tenured區(qū)。
不幸的是這些有點復雜,難于解釋。首先,Minor GC觸發(fā)Major GC,在很多情形下,將這兩者分開是不可能的。另一方面,許多現(xiàn)代垃圾收集平臺傾向于清理Tenured區(qū),因此,用“cleaning”術語僅僅是部分正確。
GC無論被稱作Major GC還是Full GC,你應該搞清楚無論GC停止所有的應用線程還是它可以和應用線程同時進行。
這個問題甚至發(fā)生在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 告訴我們更多細節(jié):
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 之后一些事情開始發(fā)生,但是與上面2次Full GC不同的是老年代的不同階段只有一次GC:
初始化標記階段,耗費0.0041705 seconds大約4ms。為了初始化標記這個階段進行“stop-the-world”。
標記和預清除并發(fā)階段,和應用先線程并行執(zhí)行。
最后重復標記階段,耗費0.0462010 seconds大約46ms。再次進行“stop-the-world”。
并發(fā)執(zhí)行清除階段,正如名稱所示,不執(zhí)行“stop-the-world”,并發(fā)實施操作。
正如我們們從GC日志看到的真實情況,事實上,替代兩次Full GC的僅僅一次Major GC清理Old space。
如果你考慮了基于jstat展示的數(shù)據(jù)的情況,你會做出正確結論。它正確展示出兩次因為所有活動線程而進行兩次“stop-the-world”,總共耗費50ms。如果你想為了吞吐量嘗試優(yōu)化,那么你會被誤導,僅僅在初始化標記和最后重復標記階段而進行“stop-the-world”,the jstat輸出完全隱藏了并發(fā)工作。
結論
綜合情況來看,這是避免考慮項目中Minor, Major or Full GC的最好方式。反之,監(jiān)控你的應用延遲或者吞吐量,將結果和GC事件聯(lián)系起來。連同這些事件一起,你額外需要相關信息,特別是GC事件強迫停止應用線程和并發(fā)處理部分事件。
如果你對本次內(nèi)容感興趣,這是目前有效的實例章節(jié)Garbage Collection Handbook。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66313.html
摘要:原文出處這種垃圾收集器的官方名稱是。使用收集器的名稱。事件時長記錄不同的類型回收期間垃圾收集器線程消耗事件調(diào)用操作系統(tǒng)活著等待系統(tǒng)事件消耗時間應用停頓的時鐘時間。現(xiàn)在我們看一些一些任務的時間,垃圾收集器線程等待很長時間。 原文出處:Concurrent Mark and Sweep 這種垃圾收集器的官方名稱是Mostly Concurrent Mark and Sweep Garbag...
摘要:堆內(nèi)存使用分析,垃圾收集器日志解讀重要的東東在中,對象實例都是在堆上創(chuàng)建。機制是由提供,用來清理需要清除的對象,回收堆內(nèi)存。在中,是由一個被稱為垃圾回收器的守護線程執(zhí)行的。 堆內(nèi)存使用分析,垃圾收集器 GC 日志解讀 重要的東東 在Java中,對象實例都是在堆上創(chuàng)建。一些類信息,常量,靜態(tài)變量等存儲在方法區(qū)。堆和方法區(qū)都是線程共享的。 GC機制是由JVM提供,用來清理需要清除的對象,...
摘要:這個算法看似不錯而且簡單,不過存在這一個致命傷當兩個對象互相引用的時候,就永遠不會被回收于是引用計數(shù)算法就永遠回收不了這兩個對象,下面介紹另一種算法。 前言 ? 如果要問Java與其他編程語言最大的不同是什么,我第一個想到的一定就是Java所運行的JVM所自帶的自動垃圾回收機制,以下是我學習JVM垃圾回收機制整理的筆記,希望能對讀者有一些幫助。 哪些內(nèi)存需要回收?what? ? ...
摘要:如果開啟,則每次后會重新計算和區(qū)的大小,計算依據(jù)是過程中統(tǒng)計的時間吞吐量內(nèi)存占用量。應用達到預期的吞吐量,即應用正常運行時間正常運行時間耗時。理論上,增大內(nèi)存,可以降低的頻率,以此達到預期吞吐量。 轉載請注明原文鏈接:https://www.jianshu.com/p/741... 一、AdaptiveSizePolicy簡介 AdaptiveSizePolicy(自適應大小策略) ...
閱讀 649·2021-11-11 16:55
閱讀 2160·2021-11-11 16:55
閱讀 1951·2021-11-11 16:55
閱讀 2341·2021-10-25 09:46
閱讀 1598·2021-09-22 15:20
閱讀 2270·2021-09-10 10:51
閱讀 1703·2021-08-25 09:38
閱讀 2613·2019-08-30 12:48