從年輕壹代空間(包括伊甸園和幸存者區域)回收內存稱為次要GC。這個定義清晰易懂。然而,當次要的GC事件發生時,有壹些有趣的事情需要註意:
當JVM不能為新對象分配空間時,比如當Eden區域已滿時,就會觸發次要GC。因此,分配率越高,次要GC執行得越頻繁。
當內存池被填滿時,所有的內容都將被復制,指針將從0開始跟蹤空閑內存。伊甸園和幸存者
該區域已被標記和復制,取代了傳統的標記、掃描、壓縮和清理操作。所以伊登和幸存者
該區域沒有內存碎片。寫指針總是停留在已用內存池的頂部。
當執行較小的GC操作時,永久生成不會受到影響。永久代對年輕代的引用視為GC根,年輕代對永久代的引用在標記階段直接忽略。
質疑常規認知,所有輕微GC都會引發“停世”。
停止應用程序的線程。對於大多數應用程序,暫停造成的延遲可以忽略不計。事實是大多數伊甸園
區域中的對象可以被視為垃圾,並且永遠不會被復制到幸存者區域或老年空間。相反,伊甸園區域的大多數新對象都不符合GC。
條件下,次要的GC執行暫停時間會長得多。
所以Minor GC的情況還是挺清楚的——每次Minor GC清理後輩的記憶。
主要GC與完全GC
應該註意的是,目前,這些術語在JVM規範或垃圾收集研究論文中沒有正式定義。但是乍看之下,我們知道這些基於我們已經知道的東西做出的定義是正確的,並且次要GC應該被設計為簡單地清理年輕磁帶的內存:
主要GC是清理永久代。
全GC是清理整個堆空間——包括年輕代和永久代。
不幸的是,它實際上有點復雜和混亂。首先,很多大GC都是由小GC觸發的,所以很多情況下不可能把這兩個GC分開。另壹方面,許多現代垃圾收集機制會清理部分永久生成空間,因此使用“清理”壹詞只是部分正確。
這就讓我們沒有必要去在意是叫大GC還是全GC。相反,我們應該註意當前的GC是否已經停止了所有的應用程序線程,或者它是否可以在不停止應用程序線程的情況下並發處理。
這種混亂甚至內置於JVM標準工具中。下面的例子很好地解釋了我的意思。讓我們比較兩個不同的工具,並發標記和清除收集器(-xx:+useConcmarkSweepgc)在JVM中運行時輸出的跟蹤記錄。
第壹次嘗試通過jstat輸出:
my-precious:me $ jstat-GC-t 4235 1s
時間S0C s 1C S0U s 1U 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.65438 +0 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 65438 +03 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秒提取的。根據這些信息,我們可以得出結論:運行了12次次要GC和2次完全GC,總時間跨度為50毫秒。通過基於GUI的工具,如jconsole或JVM,也可以得到同樣的結果。
1
Java-XX:+printgc details-XX:+UseConcMarkSweepGC eu . plumbr . demo . garbageproducer
3.157: [GC(分配失敗)3.157:[par new:272640k-& gt;34048K(306688K),0.0844702秒] 272640K->69574K(2063104K),0.0845560秒] [Times: user=0.23 sys=0.03,real=0.09秒]
4.092: [GC(分配失敗)4.092:[par new:306688k-& gt;34048K(306688K),0.1013723 secs]342214K->136584K(2063104K),0.1014307秒] [Times: user=0.25 sys=0.05,real=0.10秒]
...為了簡潔而刪減...
11.292: [GC(分配失敗)11.292:[par new:306686k-& gt;34048K(306688K),0.0857219 secs]971599k->779148K(2063104K),0.0857875秒] [Times: user=0.26 sys=0.04,real=0.09秒]
12.140: [GC(分配失敗)12.140:[par new:306688k-& gt;34046K(306688K),0.0821774秒] 1051788K->856120K(2063104K),0.0822400秒] [Times: user=0.25 sys=0.03,real=0.08秒]
12.989: [GC(分配失敗)12.989:[par new:306686k-& gt;34048K(306688K),0.1086667 secs]1128760k->;931412K(2063104k),0.1087416秒][次數:user=0.24 sys=0.04,real=0.11秒]
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
13.102:[CMS-並發-標記-開始]
13.341:[CMS-concurrent-mark:0.238/0.238秒][Times:user = 0.36 sys = 0.01,real=0.24秒]
13.341:[CMS-concurrent-pre clean-start]
13.350:[CMS-concurrent-preclean:0.009/0.009秒] [Times: user=0.03 sys=0.00,real=0.01秒]
13.350:[CMS-並發-可中止-預清除-開始]
13.878: [GC(分配失敗)13.878: [ParNew: 306688K->34047K(306688K),0.0960456秒] 1204052K->1010638k(2063104k),0.0961542秒] [Times: user=0.29 sys=0.04,real=0.09秒]
14.366:[CMS-concurrent-abor table-preclean:0.917/1.016秒] [Times: user=2.22 sys=0.07,real=1.01秒]
14.366: [GC (CMS最後備註)[YG占有率:182593K(306688K)]14.366:[重新掃描(平行),0.0291598秒]14.395:[弱引用處理,0.0000232秒]14.395:[類卸載
14.412:[CMS-並發-掃描-開始]
14.633:[CMS-concurrent-sweep:0.221/0.221秒] [Times: user=0.37 sys=0.00,real=0.22秒]
14.633:[CMS-並發-重置-啟動]
14.636:[CMS-並發-重置:0.002/0.002秒][時間:用戶=0.00系統=0.00,實際=0.00秒]
在同意這個結論之前,讓我們看壹下來自同壹個JVM啟動收集的垃圾收集日誌的輸出。顯然-xx:+printgcdetails告訴了我們壹個不同的更詳細的故事:
基於這些信息,我們可以看到12 Minor GC之後,和上面有些不同。完全垃圾收集不會運行兩次,這壹點不同,因為在永久生成的不同階段,單個垃圾收集會運行兩次:
在初始標記階段,花費了0.0041705秒,大約是4 ms,這個階段會暫停“stop-the-world”事件,停止所有應用程序線程,然後開始標記。
標記和清潔階段是並行進行的。這些都是與應用程序線程並行的。
最後壹個備註階段用了0.0462010秒約46 ms,這個階段會再次暫停所有事件。
並行執行清理操作。顧名思義,這個階段也是並行的,不會停止其他線程。
所以,從垃圾收集日誌中我們可以看到,實際上只執行了主GC來清理舊空間,而不是兩次全GC。
如果妳以後做決定,將由jstat決定。
提供的數據將指導妳做出正確的決定。它正確地列出了所有事件被掛起,導致所有線程停止* * * 50 ms的兩種情況,但如果妳試圖優化吞吐量,妳會被誤導。幹凈的
列表只列出了回收的初始標記和最終備註階段,jstat的輸出看不到並發工作。