九、CMS

一、基础概念

  • cms是以优化停顿时间为目标的一个垃圾收集器
  • 采用的是标记-清除算法
  • 其实CMS严格意义是来说是young区和old区一起回收的
  • 流程如下:
    1. 并行(jdk1.8以后)初始标记,用于标记GC ROOT以及第一个相连的对象(STW,时间短)
    2. 并发标记,跟用户线程一起行(时间长)
    3. 并行重新标记,主要是防止多标和漏标的情况(时间短)
    4. 并发清理(时间长),这个时候用户产生的垃圾就放到下一次回收
      在这里插入图片描述

二、Backgroud CMS(正常模式)

在这里插入图片描述

  • 这种情况下其实我们的GC流程还可以细分
    • 并行初始标记
    • 并发标记
      • 并发预处理
      • 可终止的预处理
    • 并行重新标记
    • 并发清理
  • 为什么会多出来两个流程呢?
    • 主要是为了防止young区引用了old区的对象。假设young区引用了old对象你咋办,你要全量扫描一次young吗,慢死你?那就进行一次minorGC在这里插入图片描述
    • 并发预处理:主要是在并行重新标记的时候,这个动作是STW,所以并发预处理就是提前先干一次这个活,去标记从young区晋升上来的对象以及old区引用改变的对象
    • 可终止的预处理:主要是堆young区进行扫描,看那些对象引用了old区的对象,然后保存在卡表中(流程在4、卡表中),然后这个时候remark的时候就很爽了。
  • CMSScheduleRemarkEdenSizeThreshold 默认值:2M
    CMSScheduleRemarkEdenPenetration 默认值:50%
    • 这两个参数的意思是:当Eden区超过2M的时候开始,当Eden区的内存超过50%就停止可终止的预处理。
    • 为什么会设置这么2个参数?
      • 主要是想要在Remark的前面发生一次minor GC这个时候卡表里面的数据就会很少了。
      • 所以还有一个参数默认是5s就是说当超过5s也会终止。或者如果发生了一次young GC也会终止。

三、记忆集

  • 进行young GC时,GC Root除了常见的栈引用、常量、静态变量、JNI、Class对象等,
  • 作用:
    • 老年代如果引用了新生代的对象,这种对象也应该要加入到我们的GC Root里面去,因为你进行young GC的时候,总不能全量扫描一遍old区把,这太扯淡了。所以就用记忆集这种数据结构保存这种引用关系。
    • 新生代引用了老年代的对象,道理相反
  • 记忆集保存非收集对象到收集对象的引用关系的集合。
  • 记忆集只是一种思想

四、卡表

  • 卡表是记忆集的实现
  • 在hotSpot中卡表是一个字节数组,数组中每一项对应内存中某一块连续的区域。如果区域中的某一个对象引用了待回收的区域的对象,就将这个数组中对应的元素变为1,否则为0
  • 卡表是使用一个字节数组实现:CARD_TABLE[],每个元素对应着其标识的内存区域一块特定大小的内存块,称为"卡页"。hotSpot使用的卡页是2^9大小,即512字节
  • 一个卡页中可包含多个对象,只要有一个对象的字段存在跨代指针,其对应的卡表的元素标识就变成1,表示该元素变脏,否则为0。GC时,只要筛选本收集区的卡表中变脏的元素加入GC Roots里。
  • 流程:
    1. 并发标记的时候,发现young区里面有引用old区的对象,然后这个A所在的区域标记为脏卡
    2. 重新标记的时候,就知道这个是被用了的,然后变成正常卡
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

五、Foregroud CMS(特殊模式)

  • CMS的另一种收集模式,只有在正常模式的并发标记阶段失败的时候,才会走到这个模式下
  • 什么是并发失败?
    • 在老年代填满之前不能完成old区的不可达对象的回收。因为这样话新来的对象是不能放入Old区的,OOM了
    • 老年代的有效内存空间不能满足晋升的需要
    • 说直白一点:就是并发标记的时候,内存不足了,OOM了
  • 怎样可以降低并发失败的几率呢?
    • 通过设置下面的参数,让old的内存达到一定比例我们就开始回收了,而不是等他满了在回收
    • -XX:CMSInitiatingOccupancyFraction
    • -XX:+UseCMSInitiatingOccupancyOnly
    • 注意:-XX:+UseCMSInitiatingOccupancyOnly 只是用设定的回收阈值(上面指定的70%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整.这两个参数表示只有在Old区占了CMSInitiatingOccupancyFraction设置的百分比的内存时才满足触发CMS的条件。注意这只是满足触发CMS GC的条件。至于什么时候真正触发CMS GC,由一个后台扫描线程决定。CMSThread默认2秒钟扫描一次,判断是否需要触发CMS,这个参数可以更改这个扫描时间间隔。
  • 如果真出现了并发失败怎么解决呢?
    • 碎片问题也是CMS采用的标记清理算法最让人诟病的地方:Backgroud CMS采用的标记清理算法会导致内存碎片问题,从而埋下发生FullGC导致长时间STW的隐患
    • 通过以下的参数控制,开始进行标记-整理算法。
      • -XX:+UseCMSCompactAtFullCollection
      • -XX:CMSFullGCsBeforeCompaction=0
      • 这两个参数表示多少次FullGC后采用MSC算法压缩堆内存,0表示每次FullGC后都会压缩,同时0也是默认值
    • 如果不开启上面的参数,那就是标记-清除算法

六、三色标记

  • 当并发标记的时候,业务线程其实也还在运行,这个时候就有可能产生多标和漏标的情况
  • 用三色标记法把GC root可达性标记对象的时候,对于“是否标记”来分颜色
  • 三色标记其实官方并没有这个说法,也没有这个概念,这个只是因为你看不懂cms源码,自己YY出来的一个东东

黑色

  • 当前对象已经被扫描,且它链上所有的对象也都被扫描了
  • 黑色对象表示应被扫描完成了,是可以存活的安全对象
  • 黑色不能跳过灰色直接指向白色

灰色

  • 当前对象已经被扫描,在链上它的下一个对象没有被扫描

白色

  • 这个对象没有被扫描
  • 可达性分析之前,所有的对象都是白色
  • 可达性分析完了之后,还是白色话就证明你是垃圾

流程

  1. 初始时,还没有开始进行可达性标记,所有对象都是白色的
  2. 开始可达性标记,首先是GC root直接引用的对象变成灰色
  3. 从灰色的集合中获取对象
  4. 将灰色集合中的对象变成黑色,并且将该对象的引用其他对象变成灰色
  5. 重复执行3,4步,直到灰色集合变空
  6. 剩下的白色对象就是垃圾了
    在这里插入图片描述
    在这里插入图片描述

多标-浮动垃圾

  • 因为并发标记,当GC线程将某个对象变成黑色后,还没进入到重新标记阶段,业务线程不引用这个对象了。但是这个对象已经是黑色了。
  • 并发标记开始后,产生的堆内的垃圾,也会直接变成黑色,不做回收
  • 多标,并不会影响正确性,其实还好,下次进行回收就行了。问题不大

漏标

  • 因为并发标记,当某个灰色对象引用的白色对象断开,然后这个白色对象又被黑色对象引用了。那么这个白色对象就成垃圾了,不会被染色了,因为黑色对象是不会在进行扫描标记的。在这里插入图片描述
  • 解决方案
    • 快照的方式:就是灰色断开的时候,快照一下,将灰色对象与白色对象的引用保存起来,然后重新标记的时候,就将灰色的为根在标记,这样把白色的就能变成黑的了
    • 增量的方式:当黑色引用白色,就将黑色变成灰色,等待重新标记扫描
  • 以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过写屏障实现的。
    • 写屏障实现原始快照(SATB): 当对象B的成员变量的引用发生变化时,比如引用消失(a.b.d = null),我们可以利用写屏障,将B原来成员变量的引用对象D记录下来
    • 写屏障实现增量更新: 当对象A的成员变量的引用发生变化时,比如新增引用(a.d = d),我们可以利用写屏障,将A新的成员变量引用对象D 记录下来

七、调优参数

CMS标记清除的全局整理

  • 由于CMS使用的是标记清除算法,而标记清除算法会有大量的内存碎片的产生,所以JVM提供了
    -XX:+UseCMSCompactAtFullCollection参数用于在全局GC(full GC)后进行一次碎片整理的工作
  • 由于每次全局GC后都进行碎片整理会较大的影响停顿时间,JVM又提供了参数
    -XX:CMSFullGCsBeforeCompaction去 控制在几次全局GC后会进行碎片整理

CMS常用参数含义

  • -XX:+UseConcMarkSweepGC
    打开CMS GC收集器。JVM在1.8之前默认使用的是Parallel GC,9以后使用G1 GC。
  • -XX:+UseParNewGC
    当使用CMS收集器时,默认年轻代使用多线程并行执行垃圾回收(UseConcMarkSweepGC开启后则默认开启)。
  • -XX:+CMSParallelRemarkEnabled
    采用并行标记方式降低停顿(默认开启)。
  • -XX:+CMSConcurrentMTEnabled
    被启用时,并发的CMS阶段将以多线程执行(因此,多个GC线程会与所有的应用程序线程并行工作)。(默认开启)
  • -XX:ConcGCThreads
    定义并发CMS过程运行时的线程数。
  • -XX:ParallelGCThreads
    定义CMS过程并行收集的线程数。
  • -XX:CMSInitiatingOccupancyFraction
    该值代表老年代堆空间的使用率,默认值为68。当老年代使用率达到此值之后,并行收集器便开始进行垃圾收集,该参数需要配合UseCMSInitiatingOccupancyOnly一起使用,单独设置无效。
  • -XX:+UseCMSInitiatingOccupancyOnly
    该参数启用后,参数CMSInitiatingOccupancyFraction才会生效。默认关闭。
  • -XX:+CMSClassUnloadingEnabled
    相对于并行收集器,CMS收集器默认不会对永久代进行垃圾回收。如果希望对永久代进行垃圾回收,可用设置-XX:+CMSClassUnloadingEnabled。默认关闭。
  • -XX:+CMSIncrementalMode
    开启CMS收集器的增量模式。增量模式使得回收过程更长,但是暂停时间往往更短。默认关闭。
  • -XX:CMSFullGCsBeforeCompaction
    设置在执行多少次Full GC后对内存空间进行压缩整理,默认值0。
  • -XX:+CMSScavengeBeforeRemark
    在cms gc remark之前做一次ygc,减少gc roots扫描的对象数,从而提高remark的效率,默认关闭。
  • -XX:+ExplicitGCInvokesConcurrent
    该参数启用后JVM无论什么时候调用系统GC,都执行CMS GC,而不是Full GC。
  • -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
    该参数保证当有系统GC调用时,永久代也被包括进CMS垃圾回收的范围内。
  • -XX:+DisableExplicitGC
    该参数将使JVM完全忽略系统的GC调用(不管使用的收集器是什么类型)。
  • -XX:+UseCompressedOops
    这个参数用于对类对象数据进行压缩处理,提高内存利用率。(默认开启)
  • -XX:MaxGCPauseMillis=200
    这个参数用于设置GC暂停等待时间,单位为毫秒,不要设置过低。

CMS的线程数计算公式

分young区的parnew gc线程数和old区的cms线程数,分别为以下两参数:

  • -XX:ParallelGCThreads=m // STW暂停时使用的GC线程数,一般用满CPU
  • -XX:ConcGCThreads=n // GC线程和业务线程并发执行时使用的GC线程数,一般较小

ParallelGCThreads

其中ParallelGCThreads 参数的默认值是:
CPU核心数 <= 8,则为 ParallelGCThreads=CPU核心数,比如4C8G取4,8C16G取8
CPU核心数 > 8,则为 ParallelGCThreads = CPU核心数 * 5/8 + 3 向下取整
16核的情况下,ParallelGCThreads = 13
32核的情况下,ParallelGCThreads = 23
64核的情况下,ParallelGCThreads = 43
72核的情况下,ParallelGCThreads = 48

ConcGCThreads
ConcGCThreads的默认值则为:
ConcGCThreads = (ParallelGCThreads + 3)/4 向下取整。
ParallelGCThreads = 1~4时,ConcGCThreads = 1
ParallelGCThreads = 5~8时,ConcGCThreads = 2
ParallelGCThreads = 13~16时,ConcGCThreads = 4

推荐配置

第一种情况:8C16G左右服务器,再大的服务器可以上G1了 没必要
-Xmx12g -Xms12g
-XX:ParallelGCThreads=8
-XX:ConcGCThreads=2
-XX:+UseConcMarkSweepGC
-XX:+CMSClassUnloadingEnabled
-XX:+CMSIncrementalMode
-XX:+CMSScavengeBeforeRemark
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=70
-XX:CMSFullGCsBeforeCompaction=5
-XX:MaxGCPauseMillis=100 // 按业务情况来定
-XX:+ExplicitGCInvokesConcurrent
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps

第二种情况:4C8G
-Xmx6g -Xms6g
-XX:ParallelGCThreads=4
-XX:ConcGCThreads=1
-XX:+UseConcMarkSweepGC
-XX:+CMSClassUnloadingEnabled
-XX:+CMSIncrementalMode
-XX:+CMSScavengeBeforeRemark
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=70
-XX:CMSFullGCsBeforeCompaction=5
-XX:MaxGCPauseMillis=100 // 按业务情况来定
-XX:+ExplicitGCInvokesConcurrent
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps

第三种情况:2C4G,这种情况下,也不推荐使用,因为2C的情况下,线程上下文的开销比较大,性能可能还不如你不动的情况,没必要。非要用,给你个配置,你自己玩。
-Xmx3g -Xms3g
-XX:ParallelGCThreads=2
-XX:ConcGCThreads=1
-XX:+UseConcMarkSweepGC
-XX:+CMSClassUnloadingEnabled
-XX:+CMSIncrementalMode
-XX:+CMSScavengeBeforeRemark
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=70
-XX:CMSFullGCsBeforeCompaction=5
-XX:MaxGCPauseMillis=100 // 按业务情况来定
-XX:+ExplicitGCInvokesConcurrent
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps

八、JDK为什么不选用CMS做垃圾收集器?

  • CMS单线程或者双线程时,效率很低
  • CMS可能会导致并发失败,从而引起full gc
  • CMS可终止的预处理在极限状态会导致5s的STW
  • CMS只是针对于停顿时间,在吞吐量上并不是很友好
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值