垃圾回收器之CMS

CMS是一款基于“标记-清除”算法的垃圾回收器,用于老年代的回收。

CMS的大概处理流程为:初始标记-并发标记-重新标记-并发清除。为什么需要重新标记呢,因为在并发标记阶段,用户线程和标记线程同时运行,这时会出现之前标记的对象发生了变化,需要等并发标记结束后,重新标记这一部分对象。当重新标记结束后,进行并发清除可回收对象。在初始标记和重新标记时,会导致stop the world,但这个时间远比并发标记时间短,可极大压缩停止时间。初始标记只是简单标记下GC ROOTS可达的节点,并发标记时进行GC ROOTS Tracing的过程。

CMS垃圾回收器有一部分缺陷。

1.抢占cpu资源,并发操作就需要创建线程,创建了额外的线程会抢占cpu资源。CMS的默认线程数计算公式为:(cpu数量+3)/4,当cpu数量大于4时,会创建一个线程,这时线程数量占25%,影响不大,但当数量小于4时,会基本占用50%的资源,严重影响业务线程的执行时间。虽然后来提供了i-cms垃圾回收器,在并发标记、清除阶段,试图通过交替运行用户线程,减少这种影响,但效果不明显。

2.无法处理浮动垃圾。在CMS进行并发清除阶段,用户线程也在运行,会产生新的对象,这部分称为浮动垃圾。CMS在清除时,无法处理浮动垃圾,而这部分数据需要内存空间存放,所以CMS不能等到内存空间完全占满时运行,在1.5版本时,达到68%的老年代空间时进行清除,但启动过早,会导致垃圾回收频繁,-XX:CMSInitiatingOccupancyFraction参数可调整阈值。在1.6版本时,此值为92%,大大提供了垃圾回收效率,但是会引发另一个问题Concurrent Mode Failure。此问题是,当预留空间无法满足浮动垃圾需求时发生的。这种情况下,CMS垃圾回收器会被强制转换为serial old,进行回收,而serial old会大大降低系统响应时间,需要更多的stop the world。

3.产生垃圾碎片。CMS垃圾回收器是基于标记清除算法的,所以会生成垃圾碎片,当大对象进来时,老年代没有足够空间提供时,会产生fullGC。CMS提-XX:UseCMSCompactAtFullCollection参数设置当老年代空间不足时,先进行整理,整理后不足才进行fullGc,但整理的过程非常慢,每次都进行整理会导致碎片问题没有了,停顿时间变长了。所以又提供了-XX:CMSFullGCsBeforeCompaction,这个参数用于设置执行多少次不整理的FullGc后,执行一次带整理的,默认值为0,每次都需要整理。

-XX:CMSInitiatingOccupancyFraction=92 和-XX:+UseCMSInitiatingOccupancyOnly

   -XX:+UseCMSInitiatingOccupancyOnly 只是用设定的回收阈值(上面指定的70%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整.

 -XX:+CMSScavengeBeforeRemark

   在CMS GC前启动一次ygc,目的在于减少old gen对ygc gen的引用,降低remark时的开销-----一般CMS的GC耗时 80%都在remark阶段

当内存增加缓慢的时候,阈值X可以设置大一点,可以使CMS回收的次数降低;如果内存增加比较快,阈值X可以设置小一点,以至于不会出现因在FullGc时空间不足,导致使用serial old回收器的情况

CMS垃圾回收器进行FullGc的情况列举如下:

1). 判断UseCMSInitiatingOccupancyFraction和UseCMSInitiatingOccupancyOnly是否为true,如果为false,设CMS根据数据决定是否要进行GC
2). 如果为true,判断老年代空间使用率是否到达UseCMSInitiatingOccupancyFraction,如果大于则GC,小于则不GC
3). 上一次ygc失败
4). 是否有gc请求,例如调用了System.gc()
5). 是否在进行堆扩容,如果是的话则GC
6). 判断是否会晋升失败(即老年代是否有空间容纳晋升对象),如果晋升失败则GC
7). 在jdk8中会判断Metaspace空间是否需要回收

FullGc条件:

  • 第一个,对象年龄太大了,这种对象一般很少,都是系统中确实需要长期存在的核心组件,他们一般不需要被回收掉,所以在新生代熬过默认15次垃圾回收之后就会进入老年代。
  • 第二个,动态年龄判定规则,如果一次新生代gc过后,发现Survivor区域中的几个年龄的对象加起来超过了Survivor区域的50%,比如说年龄1+年龄2+年龄3的对象大小总和,超过了Survivor区域的50%,此时就会把年龄3以上的对象都放入老年代。
  • 第三个,新生代垃圾回收过后,存活对象太多了,无法放入 Surviovr中,此时直接进入老年代。

可中断的预清理

可中断的预清理作用如上说的,另外有个重要意义是,如果在这个阶段如果等到了一个YGC,那么新生代会少很多无用的对象,到了重标记阶段就会少扫描很多的新生代对象,停顿时间会少很多。
和预清理的不同的是,可中断的预清理有限制条件,会根据配置的参数来决定是否需要进入或者退出该阶段,几个参数如下:

  • CMSScheduleRemarkEdenSizeThreshold:如果小于该值则不进入该阶段
  • CMSMaxAbortablePrecleanTime:如果执行时间超过该值,则退出该阶段
  • CMSScheduleRemarkEdenPenetration:如果eden使用空间大于该值,则退出该阶段
  • CMSMaxAbortablePrecleanLoops:如果执行次数大于该值,则退出该阶段

并不是内存越大越好,当内存大时,首先新生代增大,会导致youngGc时间变长,而这是最频繁的Gc,正常一次是20ms,当内存增大到16G时,会出现停顿几秒钟的情况,当停顿出现时,会导致请求积压,进而导致系统越来越慢,Gc越来越频繁。
     采用G1垃圾回收器,G1可对年轻代进行Gc停顿时间设置,可保证youngGc每次只停顿100ms,回收一部分regin区域的内存,频率会提高,但停顿变小

下面给出通用cms垃圾回收器配置参数:

-server -Xms5G -Xmx5G -server
 -XX:MaxPermSize=256M -Xss256K
 XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=512m
  -XX:+HeapDumpOnOutOfMemoryError 
  -XX:HeapDumpPath=/export/Logs
   -XX:+UseParNewGC 
   -XX:+UseConcMarkSweepGC 

   -XX:+CMSParallelRemarkEnabled 

   -XX:+UseCMSCompactAtFullCollection
   -XX:CMSFullGCsBeforeCompaction=6 

   -XX:+CMSClassUnloadingEnabled 
   -XX:+CMSPermGenSweepingEnabled 

    -XX:+UseCMSInitiatingOccupancyOnly
   -XX:CMSInitiatingOccupancyFraction=70

    -XX:SurvivorRatio=8
     -XX:NewRatio=2

卡表(Card Table)

有个场景,老年代的对象可能引用新生代的对象,那标记存活对象的时候,需要扫描老年代中的所有对象。因为该对象拥有对新生代对象的引用,那么这个引用也会被称为GC Roots。那不是得又做全堆扫描?成本太高了吧。

HotSpot给出的解决方案是一项叫做卡表(Card Table)的技术。该技术将整个堆划分为一个个大小为512字节的卡,并且维护一个卡表,用来存储每张卡的一个标识位。这个标识位代表对应的卡是否可能存有指向新生代对象的引用。如果可能存在,那么我们就认为这张卡是脏的。

在进行Minor GC的时候,我们便可以不用扫描整个老年代,而是在卡表中寻找脏卡,并将脏卡中的对象加入到Minor GC的GC Roots里。当完成所有脏卡的扫描之后,Java虚拟机便会将所有脏卡的标识位清零。

想要保证每个可能有指向新生代对象引用的卡都被标记为脏卡,那么Java虚拟机需要截获每个引用型实例变量的写操作,并作出对应的写标识位操作。

卡表能用于减少老年代的全堆空间扫描,这能很大的提升GC效率

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值