简介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));
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值