CMS收集器收集步驟:
在上一次【https://www.cnblogs.com/webor2006/p/11055468.html】中已經對CMS的垃圾收集器有了一定的理論上的了解,其中提到了CMS收集器完整的七個步驟,這一次則對每一個步驟進行一個詳細了解,並會用程序來理解這七個步驟。
- Phase1 :Initial Mark【初始標記】
這個是CMS兩次stop-the-world事件的其中一次,這個階段的目標是:標記那些直接被GC root引用或被年輕代存活對象所引用的所有對象。用圖來表示:
上面有對象是直接被GC ROOTS所指用的,有些對象是被年輕代引用的,都會被標記出來。 - Phase2 : Concurrent Mark 【並發標記】
在這個階段Garbage Collector會遍歷老年代,然后標記所有存活的對象,它會根據上個階段找到GC ROOTS遍歷查找。並發標記階段,它會與用戶的應用程序並發運行。並不是老年代所有的存活對象都會被標記,因為在標記期間用戶的程序可能會改變一些引用。如下圖:
在上面的圖中,與階段1的圖進行對比,就會發現有一個對象的引用已經發生了變化,如標黑的那個對象。
- Phase3 : Concurrent Preclean【並發預先清除】
這也是一個並發階段,與應用的線程並發運行,並不會stop應用的線程。在並發運行的過程中,一些對象的引用可能會發生變化,但是這種情況發生時,JVM會將包含這個對象的區域(Card)標記為Dirty,這也就是Card Marking。
在pre-clean階段,那些能夠從Dirty對象到達的對象也會被標記,這個標記做完之后,dirty card標記就會被清除了。
下面看下示意圖:
上圖中標紅的則為Dirty,而能夠被它所直接到達的對象也會被標記,標記完了則dirty card標記被清除,如下:

- Phase4 : Concurrent Abortable Preclean【並發可能失敗的預先清除】
這也是一個並發階段,但是同樣不會影響用戶的應用線程,這個階段是為了盡量承擔STW(stop-the-world)中最終標記階段的工作。這個階段持續時間依賴於很多的因素,由於這個階段是在重復做很多相同的工作,直接滿足一些條件(比如:重復迭代的次數、完成的工作量或者時鍾時間等) - Phase5 : Final Remark【最終重新標記】
這是第二個STW階段,也是CMS中的最后一個,這個階段的目標是標記老年代所有的存活對象,由於之前的階段是並發執行的,GC線程可能跟不上應用程序的變化,為了完成標記老年代所有存活對象的目標,STW就非常有必要了。
通常CMS的Final Remark階段會在年代代盡可能干凈的時候運行,目的是為了減少連續STW發生的可能性(年輕代存活對象過多的話,也會導致老年代涉及的存活對象會很多)。這個階段會比前面的幾個階段更復雜一些。 - 標記階段完成
經歷過以上五個階段之后,老年代所有存活的對象都被標記過了,現在可以通過清除算法去清理那些老年代不再使用的對象。可見其實CMS是將一個標記階段細分成五個子階段了。 - Phase6 : Concurrent Sweep【並發清除】
這里不需要STW,它是與用戶的應用程序並發運行,這個階段是:清除那些不再使用的對象,回收它們的占用空間為將來使用,如圖:
- Phase7 : Concurrent Reset【並發重置】
這個階段也是並發執行的,它會重設CMS內部的數據結構,為下次的GC做准備。
好,下面來對CMS進行一個總結:CMS通過將大量工作分散到並發處理階段來減少STW時間,在這塊做得非常優秀,但是CMS也有一些其它的問題,主要是如下問題:
1、CMS收集器無法處理浮動垃圾(Floating Garbage),可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生,可能引發串行Full GC;
2、空間碎片,導致無法分配大對象,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開發參數(默認就是開啟的),用於在CMS收集器頂不住要進行Full GC時開啟內存碎片的合並整理過程,內存整理的過程是無法並發的,空間碎片問題沒有了,但停頓時間不得不變長;
3、對於堆比較大的應用,GC的時間難以預估。
實踐來感受CMS的七大步驟:
關於CMS的七大步驟我們已經理論話的對它進行了學習,但是!!!還是有些抽象,所以接下來會用程序來加深對步驟的理解,可以完完整整通過實驗來看到所有的步驟,先新建一個測試類:

目前程序是平淡無奇的,接下來給JVM增加一些啟動參數,如下:




好,整個實驗需要的JVM參數都已經設定好了,接下來運行看一下輸出:
/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/bin/java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/tools.jar:/Users/xiongwei/Documents/workspace/IntelliJSpace/jvm_lectue/out/production/classes:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/mysql/mysql-connector-java/5.1.34/46deba4adbdb4967367b013cbc67b7f7373da60a/mysql-connector-java-5.1.34.jar:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/cglib/cglib/3.2.0/bced5c83ed985c080a24dc5a42b0ca631556f413/cglib-3.2.0.jar:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/5.0.3/dcc2193db20e19e1feca8b1240dbbc4e190824fa/asm-5.0.3.jar:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/org.apache.ant/ant/1.9.4/6d473e8653d952045f550f4ef225a9591b79094a/ant-1.9.4.jar:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/org.apache.ant/ant-launcher/1.9.4/334b62cb4be0432769679e8b94e83f8fd5ed395c/ant-launcher-1.9.4.jar com.jvm.gc.MyTest5 111111 [GC (Allocation Failure) [ParNew: 5119K->354K(9216K), 0.0038338 secs] 5119K->4452K(19456K), 0.0038874 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 222222 333333 [GC (Allocation Failure) [ParNew (promotion failed): 6657K->6814K(9216K), 0.0038483 secs][CMS: 8196K->8192K(10240K), 0.0041740 secs] 10755K->10573K(19456K), [Metaspace: 2650K->2650K(1056768K)], 0.0080758 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] [GC (CMS Initial Mark) [1 CMS-initial-mark: 8192K(10240K)] 14669K(19456K), 0.0005176 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] [CMS-concurrent-mark-start] [CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-preclean-start] 444444 [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-abortable-preclean-start] [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (CMS Final Remark) [YG occupancy: 6800 K (9216 K)][Rescan (parallel) , 0.0020690 secs][weak refs processing, 0.0000105 secs][class unloading, 0.0002582 secs][scrub symbol table, 0.0005401 secs][scrub string table, 0.0001471 secs][1 CMS-remark: 8192K(10240K)] 14992K(19456K), 0.0031107 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-sweep-start] [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-reset-start] [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap par new generation total 9216K, used 6964K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 85% used [0x00000007bec00000, 0x00000007bf2cd1e0, 0x00000007bf400000) from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000) concurrent mark-sweep generation total 10240K, used 8192K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) Metaspace used 2657K, capacity 4486K, committed 4864K, reserved 1056768K class space used 287K, capacity 386K, committed 512K, reserved 1048576K Process finished with exit code 0
哇~~好神奇,輸出了這么多日志,下面咱們來仔細分析一下:




下面具體來分析一下GC的日志:


這不就是七大階段的第一階段么?



接下來繼續:

這不是對應七大步驟的第二步驟么?


接着第三個階段:


對應第四個階段:


接着對應第五個步驟了,執行了一次STW了:

其中這個階段的日志信息有點多,下面解讀一下:






接着繼續:

對應第六個階段:

最后一階段:


整個CMS垃圾收集器處理完之后,接下來看一下堆的情況:


