1、特點
CMS收集器是JAVA虛擬機中垃圾收集器的一種。它運行在JAVA虛擬機的老年代中。CMS是(Concurrent MarkSweep)的首字母縮寫。CMS收集器是一種以獲取最短回收停頓時間為目標的收集器。比較適用於互聯網等場合,可能是互聯網中最重要的收集器模式;
2、優點
由於整個過程中耗時最長的並發標記和並發清除過程中,收集器線程都可以與用戶線程一起工作,所以總體上來說,CMS收集器的內存回收過程是與用戶線程一起並發地執行的。因此CMS是一款優秀的收集器,具備了並發收集、低停頓的優點,Sun的一些官方文檔裡面也稱之為並發低停頓收集器(Concurrent Low Pause Collector)。
3、缺點
CMS收集器對CPU資源非常敏感
面向並發設計的程序都對CPU資源比較敏感。在並發階段,它雖然不會導致用戶線程停頓,但是會因為占用了一部分線程(或者說CPU資源)而導致應用程序變慢,總吞吐量會降低。CMS默認啟動的回收線程數是(CPU數量+3)/ 4,也就是當CPU在4個以上時,並發回收時垃圾收集線程最多占用不超過25%的CPU資源。但是當CPU不足4個時(譬如2個),那麼CMS對用戶程序的影響就可能變得很大,如果CPU負載本來就比較大的時候,還分出一半的運算能力去執行收集器線程,就可能導致用戶程序的執行速度忽然降低了50%,這也很讓人受不了。為了解決這種情況,虛擬機提供了一種稱為“增量式並發收集器”(Incremental Concurrent Mark Sweep / i-CMS)的CMS收集器變種,所做的事情和單CPU年代PC機操作系統使用搶占式來模擬多任務機制的思想一樣,就是在並發標記和並發清理的時候讓GC線程、用戶線程交替運行,盡量減少GC線程的獨占資源的時間,這樣整個垃圾收集的過程會更長,但對用戶程序的影響就會顯得少一些,速度下降也就沒有那麼明顯,但是目前版本中,i-CMS已經被聲明為“deprecated”,即不再提倡用戶使用。
CMS收集器無法處理浮動垃圾
由於CMS並發清理階段用戶線程還在運行著,伴隨程序的運行自然還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在本次收集中處理掉它們,只好留待下一次GC時再將其清理掉。這一部分垃圾就稱為“浮動垃圾(Floating Garbage)”。也是由於在垃圾收集階段用戶線程還需要運行,即還需要預留足夠的內存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分空間提供並發收集時的程序運作使用。在默認設置下,CMS收集器在老年代使用了68%的空間後就會被激活,這是一個偏保守的設置,如果在應用中老年代增長不是太快,可以適當調高參數-XX:CMSInitiatingOccupancyFraction的值來提高觸發百分比,以便降低內存回收次數以獲取更好的性能。要是CMS運行期間預留的內存無法滿足程序需要,就會出現一次“Concurrent Mode Failure”失敗,這時候虛擬機將啟動後備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說參數-XX:CMSInitiatingOccupancyFraction設置得太高將會很容易導致大量“Concurrent Mode Failure”失敗,性能反而降低。
收集結束時會產生大量空間碎片
CMS是一款基於“標記-清除”算法實現的收集器,在收集結束時會產生大量空間碎片。空間碎片過多時,將會給大對象分配帶來很大的麻煩,往往會出現老年代還有很大的空間剩余,但是無法找到足夠大的連續空間來分配當前對象,不得不提前觸發一次Full GC。為了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關參數,用於在“享受”完Full GC服務之後額外免費附送一個碎片整理過程,內存整理的過程是無法並發的。空間碎片問題沒有了,但停頓時間不得不變長了。虛擬機設計者們還提供了另外一個參數-XX: CMSFullGCsBeforeCompaction,這個參數用於設置在執行多少次不壓縮的Full GC後,跟著來一次帶壓縮的。
4、代碼
package com.gc;
import java.util.ArrayList;
import java.util.List;
/**
* 簡單的JAVA虛擬機內存回收,cms收集器的使用
* 參數:-Xms30m -Xmx60m-Xmn10m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails
* @author 范芳銘
*/
public class EasyCMS {
public byte[] placeHolder =new byte[64 * 1024]; //占位符
public static voidmain(String[] args) throws Exception{
outOfMemoryByExpansionSize();
}
private static voidoutOfMemoryByExpansionSize() throws Exception{
List<EasyCMS>list = new ArrayList<EasyCMS>();
while(true){
EasyCMS serial =new EasyCMS();
list.add(serial);
Thread.sleep(10);//停頓10毫秒
}
}
}
5、參數
參數:-Xms30m -Xmx60m-Xmn10m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails
-Xms30m JVM最小30M
-Xmx100m JVM最大100M
-Xmn10m 新生代固定10M
-XX:+ UseConcMarkSweepGC對應parNew + cms 收集器
-XX:+PrintGCDetails 打印詳細GC
6、運行結果
…
[GC [ParNew: 9110K->972K(9216K),0.0056278 secs] 48708K->48702K(60416K), 0.0056664 secs] [Times: user=0.00sys=0.00, real=0.01 secs]
[GC [1 CMS-initial-mark:47730K(51200K)] 48766K(60416K), 0.0000829 secs] [Times: user=0.00 sys=0.00,real=0.00 secs]
[CMS-concurrent-mark: 0.003/0.003secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-preclean:0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[YG occupancy: 1036 K (9216K)][Rescan (parallel) , 0.0001092 secs][weak refs processing, 0.0000140 secs][1 CMS-remark: 47730K(51200K)] 48766K(60416K), 0.0001609 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-sweep: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-reset: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [ParNew: 9112K->9112K(9216K),0.0000185 secs][CMS: 47728K->51175K(51200K), 0.0184446 secs]56840K->56814K(60416K), [CMS Perm : 2086K->2085K(12288K)], 0.0185255secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC [1 CMS-initial-mark:51175K(51200K)] 56878K(60416K), 0.0000866 secs] [Times: user=0.00 sys=0.00,real=0.00 secs]
[CMS-concurrent-mark: 0.003/0.003secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-preclean:0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-abortable-preclean:0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[YG occupancy: 5703 K (9216K)][Rescan (parallel) , 0.0001108 secs][weak refs processing, 0.0000041 secs][1 CMS-remark: 51175K(51200K)] 56878K(60416K), 0.0001523 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-sweep: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-reset: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [ParNew: 9103K->9103K(9216K),0.0000140 secs][CMS: 51172K->51172K(51200K), 0.0135952 secs]60275K->60269K(60416K), [CMS Perm : 2085K->2084K(12288K)], 0.0136662secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC [CMS:51172K->51172K(51200K), 0.0041339 secs] 60269K->60269K(60416K), [CMS Perm: 2084K->2084K(12288K)], 0.0041729 secs] [Times: user=0.01 sys=0.00,real=0.01 secs]
[GC[1 CMS-initial-mark: 51172K(51200K)] 60269K(60416K), 0.0000883 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
…
7、CMS收集器的運作步驟
CMS收集器是基於“標記-清除”算法實現的,它的運作過程相對於前面幾種收集器來說要更復雜一些,整個過程分為6個步驟,包括:
初始標記(CMS initial mark)
並發標記(CMS concurrent mark)
並發預清理(CMS-concurrent-preclean)
重新標記(CMS remark)
並發清除(CMS concurrent sweep)
並發重置(CMS-concurrent-reset)
其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,並發標記階段就是進行GC Roots Tracing的過程,而重新標記階段則是為了修正並發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比並發標記的時間短。其他動作都是並發的。
對應具體的運行結果:
[GC [ParNew: 9110K->972K(9216K),0.0056278 secs] 48708K->48702K(60416K), 0.0056664 secs] [Times: user=0.00sys=0.00, real=0.01 secs]
【1.初始標記】
[GC [1 CMS-initial-mark:47730K(51200K)] 48766K(60416K), 0.0000829 secs] [Times: user=0.00 sys=0.00,real=0.00 secs]
【2.並發標記】
[CMS-concurrent-mark: 0.003/0.003secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
【3.並發預清理】
[CMS-concurrent-preclean:0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
【4.重新標記】
[GC[YG occupancy: 1036 K (9216K)][Rescan (parallel) , 0.0001092 secs][weak refs processing, 0.0000140 secs][1 CMS-remark: 47730K(51200K)] 48766K(60416K), 0.0001609 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
【5.並發清除】
[CMS-concurrent-sweep: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
【6.並發重置】
[CMS-concurrent-reset: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]