簡介JVM的CMS垃圾收集器

CMS(Concurrent Mark Sweep):

是一款基於“標記-清除”算法,以獲取最短GC停頓時間爲目標的垃圾收集器,B/S系統尤其青睞該收集器,以帶給用戶最優的體驗。

大部分文檔及書籍描述該收集器的收集過程爲4個,其實看GC日誌我們能知道,CMS的收集過程是分爲7個步驟的:

初始標記(CMS-initial-mark --STW):標記老年代中所有的 GC Roots能直接關聯到的對象,打印日誌:CMS-initial-mark

併發標記(CMS-concurrent-mark): 標記老年代中所有GCRoots可達的對象;打印日誌:CMS-concurrent-mark-start ,CMS-concurrent-mark

預清理(CMS-concurrent-preclean):標記引用發生了變化的對象;做一些清理工作;打印日誌:CMS-concurrent-preclean-start,CMS-concurrent-preclean

可控預清理(CMS-concurrent-abortable-preclean):可終止的CMS-concurrent-proclean;打印日誌: CMS-concurrent-abortable-preclean-start,CMS-concurrent-abortable-preclean,CMS:abort preclean due to time XXX

重新標記(CMS-Remark --STW):標記老年代中所有存活的對象;打印日誌: YG occupancy,CMS remark

併發清除(CMS-concurrent-sweep):併發清除無用對象

重置相關數據結構(CMS-concurrent-reset):重置CMS內部數據結構,準備下一次CMS GC


CMS收集器優點:併發收集,低停頓。

缺點:

1.CPU資源敏感:默認回收線程數爲(CPU數+3)/4,當CPU數多於4時,回收線程對CPU的消耗X,X>=25%&&X<=40%(這裏《深入理解java虛擬機 --周志明》一書中寫的是不超過25%,筆者關於這個問題和同事爭論了很久,但還是堅持自己的想法,有了解這方面的童鞋歡迎指正);反之,如2個CPU,回收線程對CPU的消耗會達到50%,這樣就嚴重影響了用戶線程的正常響應。

2.無法處理浮動垃圾:無法處理在回收時產生的新垃圾,因此會預留一部分空間存放新垃圾(對象),默認老年代空間被佔用達到一定百分比時,會觸發FullGC。

默認百分比計算公式:CMSInitiatingOccupancyFraction=(100 - MinHeapFreeRatio) + (CMSTriggerRatio * MinHeapFreeRatio / 100)

MinHeapFreeRatio:空餘堆內存小於40%(JDK1.7默認40%)時,JVM就會增大堆直到-Xmx的最大限制,CMSTriggerRatio:JDK1.7默認爲80%,因此jdk1.7的CMSInitiatingOccupancyFraction的默認值爲92%,也可適當調整該參數以獲取較好的性能,通常和-XX:+UseCMSInitiatingOccupancyOnly配合使用,如果沒有開啓該參數,JVM只是用CMSInitiatingOccupancyFraction所設定的值進行第一次CMS GC,後面都是使用JVM動態計算出來的值來進行GC。當然該參數並不是越大越好,參數過大,導致預留的空間變小,在執行CMS GC的過程中同時有對象要放入老年代,但是老年代沒有足夠的連續空間時,會拋出Concurrent Mode Failure的錯誤,此時不得不啓用備用收集器Serial Old進行GC,這樣停頓的時間就會變得很長,因此,CMSInitiatingOccupancyFraction的值通常建議設置爲存活對象(即MinorGC後被轉移到老年代的對象)大小的1.5倍,當然,這也只是一個估值,不可能很準確。

3.產生內存碎片

CMS是基於“標記-清除”算法的收集器,這意味着垃圾回收完成後會產生大量的內存碎片,當大對象沒有足夠的連續空間來分配時,不得不提前觸發一次Full GC。

可打開UseCMSCompactAtFullCollection參數,每次Full GC後會進行一次碎片整理,碎片整理時是無法併發的,因此會拖長停頓時間;也可設置CMSFullGCsBeforeCompaction參數值,用於執行多少次不帶碎片整理的Full GC後來一次碎片整理。


下面簡單測試下CMS收集器各參數的使用效果:

另:參數調整、測試結果及結論我都寫在了註釋裏,方便對比效果。


import java.util.Random;

/**
 * 測試CMS收集器 基於標記-清除算法 以最短GC停頓時間爲目標的收集器
 * ====================Test CMS && 新生代:老年代=1:1 VS 新生代:老年代=1:2====================
 * 
 * ====================新生代:老年代=1:1====================
 * -XX:+UseConcMarkSweepGC -Xms1024m -Xmx1024m -Xmn512m -XX:+PrintGCDetails
 * 第一次:
 * runtime:	272715.482610ms
 * 新生代GCtime:3643次,耗時79198ms,平均GC耗時21.7398ms
 * 老年代GCtime:3302次,耗時54467ms,平均GC耗時16.4952ms
 * 總GCtime:6945	次,耗時133665ms,吞吐量50.99%,concurrent mode failure次數749次,promotion failed次數216次
 * 
 * 第二次:
 * runtime:249985.544583ms
 * 新生代GCtime:3601次,耗時71791ms,平均GC耗時19.9364ms
 * 老年代GCtime:3283次,耗時53024ms,平均GC耗時15.1794ms
 * 總GCtime:6884次,耗時121625ms,吞吐量51.35%,concurrent mode failure次數737次,promotion failed次數223次
 * 
 * 第三次:
 * runtime:246530.032659ms
 * 新生代GCtime:3634次,耗時72208ms,平均GC耗時19.8701ms
 * 老年代GCtime:3310次,耗時48827ms,平均GC耗時14.7514ms
 * 總GCtime:6944次,耗時121035ms,吞吐量50.9%,concurrent mode failure次數710次,promotion failed次數264次
 * 
 * ====================新生代:老年代=1:2====================
 * -XX:+UseConcMarkSweepGC -Xms1024m -Xmx1024m -XX:+PrintGCDetails
 * 第一次:
 * runtime:359245.357208ms
 * 新生代GCtime:6271次,耗時153111ms,平均GC耗時24.4157ms
 * 老年代GCtime:3925次,耗時68898ms,平均GC耗時17.5536ms
 * 總GCtime:10196次,耗時222009ms,吞吐量38.2%,concurrent mode failure次數1153次,promotion failed次數180次
 * 
 * 第二次:
 * runtime:350564.980612ms
 * 新生代GCtime:6253次,耗時150799ms,平均GC耗時24.1163ms
 * 老年代GCtime:3921次,耗時66580ms,平均GC耗時16.9804ms
 * 總GCtime:10174次,耗時217379ms,吞吐量37.99%,concurrent mode failure次數1141次,failed次數190次
 * 
 * 第三次:
 * runtime:352798.367248ms
 * 新生代GCtime:6269次,耗時150110ms,平均GC耗時23.9448ms
 * 老年代GCtime:3921次,耗時68850ms,平均GC耗時17.5592ms
 * 總GCtime:10190次,耗時218960ms,吞吐量37.94%,concurrent mode failure次數1162次,promotion failed次數182次
 * 
 * 測試結論:新生代和老年代的配比調整後(1:2),老年代空間比新生代大,但是新生代空間縮小引起的minor GC頻率高了近一倍,進而導致老年代的GC頻率也變高,儘管老年代空間變大,它的GC次數
 * 也還是會增多,cmf錯誤增多,pf錯誤有輕微改善,吞吐量大幅下降
 * 
 * ====================Test CMS && -XX:-UseCMSInitiatingOccupancyOnly VS -XX:+UseCMSInitiatingOccupancyOnly====================
 * ====================-XX:-UseCMSInitiatingOccupancyOnly====================
 * -XX:+UseConcMarkSweepGC -Xms1024m -Xmx1024m -XX:+PrintGCDetails -XX:CMSInitiatingOccupancyFraction=60 -XX:-UseCMSInitiatingOccupancyOnly
 * 第一次:
 * runtime:358914.776261ms
 * 新生代GCtime:6272次,耗時156202ms,平均GC耗時24.9047ms
 * 老年代GCtime:3887次,耗時68298ms,平均GC耗時17.5788ms
 * 總GCtime:10159次,耗時224500ms,吞吐量37.45%,concurrent mode failure次數1164次,promotion failed次數176次
 * 
 * 第二次:
 * runtime:342977.466012ms
 * 新生代GCtime:6235次,耗時147295ms,平均GC耗時23.6239ms
 * 老年代GCtime:3887次,耗時66026ms,平均GC耗時16.9863ms
 * 總GCtime:10122次,耗時213321ms,吞吐量37.8%,concurrent mode failure次數1133次,promotion failed次數178次
 * 
 * 第三次:
 * runtime:353636.902574ms
 * 新生代GCtime:6339次,耗時149985ms,平均GC耗時23.6607ms
 * 老年代GCtime:3969次,耗時69797ms,平均GC耗時17.5855ms
 * 總GCtime:10308次,耗時219782ms,吞吐量37.85%,concurrent mode failure次數1188次,promotion failed次數194次
 * 
 * ====================-XX:+UseCMSInitiatingOccupancyOnly====================
 * -XX:+UseConcMarkSweepGC -Xms1024m -Xmx1024m -XX:+PrintGCDetails -XX:CMSInitiatingOccupancyFraction=60 -XX:+UseCMSInitiatingOccupancyOnly
 * 第一次:
 * runtime:341280.937406ms
 * 新生代GCtime:6344次,耗時147641ms,平均GC耗時23.2725ms
 * 老年代GCtime:3513次,耗時67306ms,平均GC耗時19.1591ms
 * 總GCtime:9857次,耗時214947ms,吞吐量37.02%,concurrent mode failure次數1287次,promotion failed次數147次
 * 
 * 第二次:
 * runtime:340576.789017ms
 * 新生代GCtime:6288次,耗時146703ms,平均GC耗時23.3306ms
 * 老年代GCtime:3449次,耗時66673ms,平均GC耗時19.3311ms
 * 總GCtime:9737次,耗時213376ms,吞吐量37.35%,concurrent mode failure次數1228次,promotion failed次數179次
 * 
 * 第三次:
 * runtime:338139.391526ms
 * 新生代GCtime:6298次,耗時147962ms,平均GC耗時23.4935ms
 * 老年代GCtime:3457次,耗時64833ms,平均GC耗時18.7541ms
 * 總GCtime:9755次,耗時212794ms,吞吐量37.07%,concurrent mode failure次數1222次,promotion failed次數175次
 * 
 * 測試結論:
 * 1.配置CMSInitiatingOccupancyFration,未啓用UseCMSInitiationOccupancyOnly,觀察日誌可發現(GC [1 CMS-initial-mark: 430084K(707840K)]),430084/707840=60.76%
 * 老年代空間佔用60%後,觸發了第一次CMS GC,CMS開始做初始化標記,第二次CMS GC被觸發時,老年代空間佔用率([GC [1 CMS-initial-mark: 332806K(707840K)]),332806/707840=47%,
 * 第三次CMS GC被觸發時,老年代空間佔用率([GC [1 CMS-initial-mark: 364030K(707840K)]),364030/707840=51%,由此可見,未啓用CMSInitiatingOccupancyOnly參數時,
 * CMSInitiatingOccupancyFraction參數只用作第一次CMSGC,後面都是JVM動態計算後進行的CMS GC。
 * 2.啓用UseCMSInitiatingOccupancyOnly後,觀察日誌可發現,每次CMS GC開始初始化標記,老年代空間佔用率均>=60%,老年代的GC次數明顯減少,但是cmf錯誤也隨之增多,老年代單次GC時間
 * 變長
 * 
 * ====================Test CMS && -XX:+UseCMSCompactAtFullCollection====================
 * -XX:+UseConcMarkSweepGC -Xms1024m -Xmx1024m -XX:+PrintGCDetails -XX:CMSInitiatingOccupancyFraction=60 -XX:+UseCMSCompactAtFullCollection
 * 第一次:
 * runtime:328494.673131ms
 * 新生代GCtime:6240次,耗時140340ms,平均GC耗時22.4904ms
 * 老年代GCtime:3932次,耗時63540ms,平均GC耗時16.1597ms
 * 總GCtime:10172次,耗時203880ms,吞吐量37.94%,concurrent mode failure次數1165次,promotion failed次數191次
 * 
 * 第二次:
 * runtime:331893.002815ms
 * 新生代GCtime:6257次,耗時142251ms,平均GC耗時22.7347ms
 * 老年代GCtime:3929次,耗時63729ms,平均GC耗時16.2202ms
 * 總GCtime:10186次,耗時205979ms,吞吐量37.94%,concurrent mode failure次數1141次,promotion failed次數187次
 * 
 * 第三次:
 * runtime:331561.623350ms
 * 新生代GCtime:6204次,耗時142546ms,平均GC耗時22.9764ms
 * 老年代GCtime:3870次,耗時63457ms,平均GC耗時16.3971ms
 * 總GCtime:10074次,耗時206003ms,吞吐量37.87%,concurrent mode failure次數1120次,promotion failed次數191次
 * 
 * 測試結論:理論上開啓UseCMSCompactAtFullCollection參數後,要消耗時間去進行內存碎片整理,GC的性能會有所下降,體現在單次GC時間變成,吞吐量下降,碎片少了,老年代的GC次數以及cmf錯誤理論上也會減少,
 * 但是此處測試並未證實該結論,可能是測試代碼中此處的cmf錯誤並不是內存碎片引起的。
 * 
 * 
 * @author ljl
 */
public class TestCMSGC {
	private static int _5MB = 5 * 1024 * 1024;
	
	public static void main(String[] args) {
		byte[] memory = null;
		Random rm = new Random();
		int j;
		long startTime = System.nanoTime();
		for (int i = 0; i < 10000; i++) {
			j = rm.nextInt(10) + 10;
			System.out.println("j * _5MB = " + j * 5);
			memory = new byte[j * _5MB];
			
		}
		System.out.println("runtime = " + (System.nanoTime() - startTime));
	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章