什么是CMS
CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器,
如果老年代使用CMS垃圾回收器,需要添加虚拟机参数-”XX:+UseConcMarkSweepGC”。
使用场景:
GC过程短暂停,适合对时延要求较高的服务,用户线程不允许长时间的停顿。
缺点:
服务长时间运行,造成严重的内存碎片化。
另外,算法实现比较复杂(如果也算缺点的话)
实现机制
根据GC的触发机制分为:周期性Old GC(被动)和主动Old GC
个人理解,实在不知道怎么分才好。
周期性Old GC
周期性Old GC,执行的逻辑也叫Background Collect,对老年代进行回收,在GC日志中比较常见,由后台线程ConcurrentMarkSweepThread循环判断(默认2s)是否需要触发。
触发条件
1、如果没有设置-XX:+UseCMSInitiatingOccupancyOnly,虚拟机会根据收集的数据决定是否触发(建议线上环境带上这个参数,不然会加大问题排查的难度)。
2、老年代使用率达到阈值 CMSInitiatingOccupancyFraction,默认92%。
3、永久代的使用率达到阈值 CMSInitiatingPermOccupancyFraction,默认92%,前提是开启 CMSClassUnloadingEnabled。
4、新生代的晋升担保失败。
晋升担保失败
老年代是否有足够的空间来容纳全部的新生代对象或历史平均晋升到老年代的对象,如果不够的话,就提早进行一次老年代的回收,防止下次进行YGC的时候发生晋升失败。
周期性Old GC过程
当条件满足时,采用“标记-清理”算法对老年代进行回收,过程可以说很简单,标记出存活对象,清理掉垃圾对象,但是为了实现整个过程的低延迟,实际算法远远没这么简单,整个过程分为如下几个部分:
对象在标记过程中,根据标记情况,分成三类:
白色对象,表示自身未被标记;
灰色对象,表示自身被标记,但内部引用未被处理;
黑色对象,表示自身被标记,内部引用都被处理;
假设发生Background Collect时,Java堆的对象分布如下:
1、InitialMarking(初始化标记,整个过程STW)
该阶段单线程执行,主要分分为两步:
标记GC Roots可达的老年代对象;
遍历新生代对象,标记可达的老年代对象;
该过程结束后,对象分布如下:
2、Marking(并发标记)
该阶段GC线程和应用线程并发执行,遍历InitialMarking阶段标记出来的存活对象,然后继续递归标记这些对象可达的对象。
因为该阶段并发执行的,在运行期间可能发生新生代的对象晋升到老年代、或者是直接在老年代分配对象、或者更新老年代对象的引用关系等等,对于这些对象,都是需要进行重新标记的,否则有些对象就会被遗漏,发生漏标的情况。
为了提高重新标记的效率,该阶段会把上述对象所在的Card标识为Dirty,后续只需扫描这些Dirty Card的对象,避免扫描整个老年代。
3、Precleaning(预清理)
通过参数CMSPrecleaningEnabled选择关闭该阶段,默认启用,主要做两件事情:
处理新生代已经发现的引用,比如在并发阶段,在Eden区中分配了一个A对象,A对象引用了一个老年代对象B(这个B之前没有被标记),在这个阶段就会标记对象B为活跃对象。
在并发标记阶段,如果老年代中有对象内部引用发生变化,会把所在的Card标记为Dirty(其实这里并非使用CardTable,而是一个类似的数据结构,叫ModUnionTalble),通过扫描这些Table,重新标记那些在并发标记阶段引用被更新的对象(晋升到老年代的对象、原本就在老年代的对象)
4、AbortablePreclean(可中断的预清理)
该阶段发生的前提是,新生代Eden区的内存使用量大于参数CMSScheduleRemarkEdenSizeThreshold 默认是2M,如果新生代的对象太少,就没有必要执行该阶段,直接执行重新标记阶段。
作者:java菜
链接:https://www.jianshu.com/p/35ac7e2537a8
共同学习,写下你的评论
评论加载中...
作者其他优质文章