当前位置:   article > 正文

JVM CMS垃圾收集器简聊

JVM CMS垃圾收集器简聊

CMS简介:

  • CMS是老年代的垃圾收集器
  • CMS是以最短回收停顿时间为目标的垃圾回收器,采用的是标记-清除算法
  • 适用于对服务器响应速度较高的场景
JDK8不用CMS作为默认垃圾收集器的原因:
  • CMS单线程或者双线程的情况下效率很低
  • CMS会并发失败
  • CMS可终止的预处理会导致极限停顿5秒
  • 并发失败进入foreground,还会呆滞进入Full GC,全局MSC整理
  • CMS的吞吐设计并不是很优秀

CMS两种策略:

  • Foreground CMS
  • Background CMS

Background CMS回收流程

  1. 初始标记:标记GC root直接关联对象,不用标记关联链上的对象,速度很快,暂停所有用户线程(JDK7及以前是单线程,JDK8以后默认是多线程。-XX:+CMSParallelInitialMarkEnable 可改变是否多线程)
  2. 并发标记:并发标记第一步标记的GC root直接关联对象链上的对象,速度慢,用户线程运行中
  3. 重新标记:修改因并发标记过程中用户线程变动的对象,速度快,暂停所有用户线程
  4. 并发清除:清除不可达对象回收空间,这段时间产生新垃圾,留着下次清理,称为浮动垃圾

由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作
在这里插入图片描述
优点:并发收集,低停顿
缺点:产生大量空间锁片,并发阶段会降低吞吐量

并发预处理和可中止的预处理可以认为是并发标记的策略。

-XX:CMSScheduleRemarkEdenSizeThreshold=2M
-XX:CMSScheduleRemarkEdenPenetration=50%
这两个参数组合起来,是与清理之后,eden区空间使用超过2M的时候启动 可中断的并发预处理(CMS-concurrent-abortable-preclean),到Eden区空间使用率到达50%的时候中断(非结束),进入Remark(重新标记阶段)
可中断意味着,如果一直处理(预处理就是把正式应该处理的前置工作做了),需要设置一个时间进行打断。所以并发预处理的逻辑是发生了Minor GC的时候,预处理就结束了。

问题1:什么时候发生Minor GC?不知道,垃圾回收是JVM自动调度的,无法控制。

为了不会无限执行下去,需要一个结束时间:
-XX:CMSMaxAbortablePrecleanTime=5s 默认值,只要到了5s,不管发生没发生Minor GC,有没有到CMSScheduleRemarkEdenPenetration都会中止此阶段,进入remark。

问题2:如果到了5s没有执行Minor GC怎么办?

-XX:+CMSScavengeBeforeRemark,是remark之前强制进行一次Minor GC。

Foreground CMS:

这也是CMS的一种手机模式,但是需要是并发失败才会走的模式
并发失败的原因:如果并发搜集器不能在老年代填满前完成不可达对象的回收,或者老年代中有效空闲内存空间不能满足某一个内存的分配请求,此时应用会被暂停,并在此暂停期间开始卡机回收,直到回收完成才会恢复应用。这种无法并发完成搜集的情况称为并发失败,这种情况的发生意味着我们需要调节并发搜集器的参数了。
简单来说,就是并发标记的时候,内存不够用了,这个时候会暂停所有线程,并且开始全局Full GC。
问题1:什么时候会并发失败呢?或者说非要满了才进行收集吗?
-XX:CMSInitiatingOccupancyFraction=70%
-XX:+UseCMSInitiatingOccupancyOnly
CMSInitiatingOccupancyFraction只是用来设定回收阈值,如果不指定JVM尽在第一次使用设定阈值,后续会自动调整。这两个参数表示只有Old区占了设置的百分比的内存时才满足CMS的条件。
这里只是满足了CMS GC的条件,至于什么时候真正出发CMS GC,有一个后台扫描线程决定。CMSThread默认2s扫描一次,判断是否需要触发CMS,

记忆集

记忆集是一种用于记录从非收集区域执行收集区域的指针集合的数据结构。是针对跨带引用问题提出的思想,由此产生了实现

卡表 CARD_TABLE

是一个字节数组,每个元素对应这其表示的内存区域一块特定带下的内存块,称为卡页。hotSpot使用卡页是2^9,也就是512字节。
每一个可也中可包含多个对象,只有一个对象的字段存在跨带指针,其对应的卡表元素标识就变成1,标识该元素变脏,否则为0.只要筛选本收集区中卡表变脏的元素加入GC root即可

CMS的标记压缩算法:MCS(Mark Sweep Compact)

滑动整理,两个参数
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCBeforeCompaction=2
CMSFullGCBeforeCompaction表示多少次Full GC之后采用MSC压缩堆内存,0表示每次Full GC后都会压缩,默认值是0。

三色标记

在并发标记过程中,因为标记期间应用线程还在继续,对象间的引用可能会发生变化,多标或漏标的情况就有可能发生。这里引入三色标记来详细解释下,把GC root可达性分析遍历对象过程中遇到的对象,按照是否访问过这个条件标记成一下三种颜色:

  • 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。代表对象已经扫描过,他是安全存活的。如果没有其他对象引用指向了黑色对象,无需扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
  • 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描。
  • 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束阶段,仍然是白色的,及代表不可达。

多标-浮动垃圾

在并发标记过程中,如果由于方法运行结束导致的部分局部变量(GC root)被销毁,这个GC root引用的对象之前又被扫描过(被标为非垃圾对象),那么本轮GC不会回收这一部分内存。这部分本应该回收但是没有回收的内存,被称为浮动垃圾,浮动垃圾不会影响垃圾回收的正确性,只是需要等到下一次垃圾回收才被清理

漏标-读写屏障

漏标只有同时满足一下两个条件是才会发生:

  • 条件1:灰色对象断开了白色对象的引用,即灰色对象原来成员变量的引用发生了变化
  • 条件2:黑色对象重新引用了该白色对象;即黑色对象成员变量增加了新的引用
    漏标会导致被引用的对象被当成垃圾误删除,这是严重的bug,必须解决,有两种解决方案:
  • 增量更新:
    • 就是当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等病啊扫描结束之后,再讲这些记录过的引用关系中的黑色对象韦根,重新扫描一次。可以简单理解为,黑色对象一旦新插入了指向白色对象的引用之后,他就变成灰色对象了。
  • 原始快照:原始快照就是当灰色对象要删除指向白色对象的引用关系是,九江这个要删除的引用记录下来,在并发扫描结束之后,再讲这些记录过的引用关系中的灰色对象为根,重新扫描一次,这样就能扫描到白色的对象,将这个白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能村会下来,代下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)
    以上无论是对应用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。

CMS常用参数及含义:
-XX:+UseConcMarkSweepGC 打开CMS GC收集器。JDK在1.8之前默认使用Parallel GC,9之后使用G1。
-XX:+UseParNewGc 当时用CMS收集器是,默认年轻待使用多线程并行执行垃圾回收(UseConcMarkSweepGC开启后默认开启)

-XX:+CMSParallelRemarkEnabled 采用并行标记方式降低停顿(默认开启)
-XX:+CMSConcurrentMTEnabled 被启用时,并发的CMS阶段讲义多线程执行(因此多个GC线程会与所有的应用程序线程并行工作,默认开启)
-XX:ConcGCThreads=2 定义并发CMS过程运行时的线程数
-XX:ParallelGCThreads 定义CMS过程并行性手机的线程数

-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=68% 代表老年代堆空间使用率,默认68.当老年代使用率达到此值后,并行收集器开始进行垃圾收集。该参数需要配合UseCMSInitiatingOccupancyOnly一起使用,单独无效。

-XX:+CMSClassUnloadingEnabled 相对于并行收集器,CMS收集器默认不会对永久代进行垃圾回收,如果希望对永久代进行垃圾回收,可以设置该参数。默认关闭。

-XX+CMSIncrementalMode 开启CMS收集器的增量模式。增量模式是的回收过程更长,但是暂停时间往往更短,默认关闭。

-XX:CMSFullGCsBeforeCompaction 设置多少次Full GC之后对内存空间进行压缩整理,默认为0。

-XX:+CMSScavengeBeforeRemark 在CMS GC remark之前做一次Young GC,减少GC root扫描的对象数,从而提高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

CMS推荐配置参数:

第一种情况:8核16G左右服务器,再大的服务器可以上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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

第二种情况:4核8G

-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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

第三种情况:2核4G,这种情况下,也不推荐使用,因为2核的情况下,线程上下文的开销比较大,性能可能还不如你不动的情况,没必要。非要用,给你个配置,你自己玩。

-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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/561929
推荐阅读
相关标签
  

闽ICP备14008679号