java垃圾回收——CMS

what:

  CMS全称Concurrent marke sweep,中文是并发标记清除算法。

  CMS出现的目的是:尽可能的减少STW(stop the world)的时间。

 

大体流程:

 

 注意:“预清理”和“可终止清理”没有标记出来

 

how:

  CMS工作分7步,分别是:

  1、初始标记;

    STW的方式工作。标记出根对象(GC root直接引用的对象),从线程栈、静态区(方法区的静态属性引用对象、方法区的常量引用对象)、本地方法栈的JNI引用的对象。

    包含2部分:a、老年代中全部的GC root对象,如下图的1结点;b、年轻代中活着的对象,它们所引用到的老年代中的对象,如下图的节点2、3

  

  2、并行标记;

     以“初始标记”的对象集为起点,并行的找到所有存活的对象;由于该阶段是和用户线程同时进行的,所以对象之间的引用关系会发生改变(如:新生代升到老年代、老年代中的对象引用关系变化等)。如果不进行后续处理,会出现“漏标”的情况。即有了后面的“重新标记”步骤。

    为了提高“重新标记”的效率,会将“并行标记”阶段变化的对象所在的card标记为dirty,后续只扫描标记为dirty的card。

    该阶段会增加2个节点4和5。

  3、预清理;

    由于“并发标记”阶段,并不能标记所有存活的对象(原因是:并行)。在该阶段会扫描所有dirty的card。如下图:“并发标记”阶段,节点3的引用关系发生了变化,所有节点3所在的card会被标记成dirty。

 

    扫描后,6节点会被标记上,如下图;

    

 

 

 

  4、可终止预清理;

    该阶段主要是为下阶段的工作铺垫,尽量承担一部分下阶段的工作,该阶段持续的事件依赖很多因素,例如:重复次数、持续工作时间登。

    目的是:期待在该阶段中发生异常YGC,从而减少下个阶段扫描新生代对老年代引用的时间。

  5、重新标记;

    目的:标记老年代中所有存活的对象。

    本阶段标记的范围是:整个堆,包含young和old代。为何扫描young代?old代中的对象只要是被young代中的对象引用了,就算是活的。即使young中的对象是不可达,也会用来做gc root扫描old代。

    在第2到第4阶段,都是和用户线程并行的,young代中对old代中的对象引用关系发生了变化,所以remark要化很长时间来STW做标记。则CMS会尽量在做final remark时,young代是足够干净的。

    可以加入参数-XX:+CMSScavengeBeforeRemark,在重新标记之前,先执行一次ygc。

  6、并发清理

    此阶段是:完成前5阶段标记后剩下的垃圾对象。

    在此阶段的时间内,用户新产生的垃圾,就只能等到下个阶段在清理了。

  7、并发重置;

    重置CMS内部的数据结构,准备给下个阶段的CMS使用。

 

注意:

  1、减少final remark的时间:

   一般CMS的80%时间来自“final remark”,如果发现该阶段的时间比较长,可以考虑使用如下参数:

    -XX:+CMSScavengeBeforeRemark

 

  2、内存碎片:

  CMS是基于“标记-清理”原理来做的。
  一般通过:CMSFullGCsBeforeCompaction来设计压缩的。CMSFullGCsBeforeCompaction默认是0。

 

 

 

  3、concurrent mode failure

  CMS除了“初始标记”和“重新标记”2个阶段会STW,其他阶段都是和用户进程并发的。就会出现:用户线程向老年代不断提交新的对象,但是老年代还没来得及腾出空间,老年代空间满了,就出现了concurrent mode failure。

  可以通过调整:-XX:CMSInitiatingOccupancyFraction和-XX:+UseCMSInitiatingOccupancyOnly。二者关系:如果不设置UseCMSInitiatingOccupancyOnly,只设置XX:CMSInitiatingOccupancyFraction。那么JVM只会第一次使用XX:CMSInitiatingOccupancyFraction,后面会自动调整XX:CMSInitiatingOccupancyFraction,从而导致设置的不在有效。

  注意:如果XX:CMSInitiatingOccupancyFraction设置大了会fullGC,小了会频繁cms gc,那就说明老年代的空间小了(需要增大)。

  危害:“concurrent mode failure”会导致fullGC变成Serial Old收集器,它是单线程的标记-压缩收集器,所以耗时非常的长。

如下面的例子:

  

 

 

  4、promotion failed

  发生在年轻代回收。一般是:Minor GC时,Survivor Space放不下,对象只能放入老年代,而此时老年代也放不下造成的。

  解决方案:a、增大Survivor空间;b、减少对象的大小;

 

posted @ 2021-09-03 00:04  修心而结网  阅读(1769)  评论(0编辑  收藏  举报