JVM学习笔记(5):CMS垃圾回收器工作原理详解

  • Post author:
  • Post category:其他




老年代回收容器——CMS



1、CMS工作原理

一般老年代我们选择的垃圾回收器是CMS,他采用的是

标记清理算法

(不是标记整理)。之前提到过

Stop the World

状态,就是垃圾回收时停止一切线程的工作,如果在这个状态下再去慢慢执行标记清理算法,会导致系统卡死时间过长,所以CMS垃圾回收器采取的是

垃圾回收线程和系统工作线程尽量同时执行的模式

来处理的。


工作原理

:为了避免长时间

Stop the World

,CMS采用了4个阶段来垃圾回收,分别是初始标记、并发标记、重新标记和并发清理。其中初始标记和重新标记,耗时很短,虽然会导致

Stop the World

,但是影响不大,然后并发标记和并发清理,两个阶段耗时最长,但是是可以跟系统的工作线程并发运行的,所以对系统没太大影响。



2、CMS垃圾回收的四个阶段



(1)初始标记——标记直接GC Roots(直接)

在这个阶段让系统的工作线程全部停止,进入

Stop the World

状态。同时

标记所有GC Roots直接引用的对象

,是直接引用!比如下面这段代码,仅仅会通过

replicaManager

这个类的静态变量代表的GC Roots,去标记出来他直接引用的

ReplicaManager

对象,不会去管

ReplicaFetcher

这种对象,因为

ReplicaFetcher

对象是被

ReplicaManager

类的

replicaFetcher

实例变量引用的。(之前说过,方法的局部变量和类的静态变量是GC Roots。但是类的实例变量不是GC Roots。)

public class Kafka {
    private static ReplicaManager replicaManager = new ReplicaManager();
}
public class ReplicaManager {
    private ReplicaFetcher replicaFetcher = new ReplicaFetcher();
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fk34GSB4-1581153515938)(/Users/zhangye/Library/Application Support/typora-user-images/image-20191023210028357.png)]

初识标记如图所示



(2)并发标记——对老年代所有对象进行GC Roots追踪(最耗时)

这个阶段会让系统线程可以随意创建各种新对象,继续运行。在运行期间可能会创建新的存活对象,也可能会让部分存活对象失去引用,变成垃圾对象。在这个过程中,垃圾回收线程,会尽可能的对已有的对象进行GC Roots追踪。GC Roots追踪,意思就是对类似

ReplicaFetcher

之类的全部老年代里的对象,他会去看他被谁引用了,

认定为是被GC Roots间接引用后,就不需要回收它。因为老年代里存活对象是比较多的,这个过程会追踪大量的对象,所以耗时较高。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4a1EWItV-1581153515940)(/Users/zhangye/Library/Application Support/typora-user-images/image-20191023210352901.png)]



(3)重新标记

第二阶段里,你一边标记存活对象和垃圾对象,一边系统在不停运行创建新对象,让老对象变成垃圾,所以第二阶段结束之后,绝对会有很多存活对象和垃圾对象,是之前第二阶段没标记出来的。在这个阶段,要再次进入

Stop the World

阶段,重新标记下在第二阶段里新创建的一些对象,还有一些已有对象可能失去引用变成垃圾的情况。重新标记的阶段只是对变动过的

少数对象

进行标记,是速度很快的



(4)并发清理

这个阶段就是让系统程序随意运行,然后清理掉之前标记为垃圾的对象即可,也是很耗时的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cfWztAVu-1581153515941)(/Users/zhangye/Library/Application Support/typora-user-images/image-20191023210811272.png)]



3、CMS性能分析



(1)好的方面

CMS的第二阶段和第四阶段,都是很耗时的,但都和系统程序是并发执行的,所以基本这两个最耗时的阶段对性能影响不大。只有第一个阶段和第三个阶段是需要

Stop the World

的,但是这两个阶段都是简单的标记而已,速度非常的快,所以基本上对系统运行响应也不大。



(2)坏的方面



①并发回收导致CPU资源紧张。

并发标记和并发清理两个最耗时的阶段,使垃圾回收线程和系统工作线程同时工作,导致有限的CPU资源被垃圾回收线程占用了一部分。在这两个阶段,CMS的垃圾回收线程是比较耗费CPU资源的。CMS默认启动的垃圾回收线程的数量是(CPU核数 + 3)/ 4,比如的2核4G机器,就会占用(2+3)/4 = 1个CPU被用来垃圾回收。



②Concurrent Mode Failure问题

在并发清理阶段,CMS只不过是回收之前标记好的垃圾对象,但这个时候系统一直在运行,先把某些对象分配在新生代,然后可能触发了一次Minor GC,一些对象进入了老年代,在短时间内又没人使用这些对象,这种垃圾对象就是

浮动垃圾

,虽然它是垃圾,但是不会回收他们,要等到下一次才能回收。


CMS垃圾触发的时机是当老年代内存占用到达一定比例时,就会自动GC



-XX:CMSInitiatingOccupancyFaction

这个参数可以设置老年代内存占用到多少比例时触发垃圾回收。JDK 1.6默认是92%。预留8%的空间给并发回收期间,系统程序把一些新对象放入老年代中。如果垃圾回收期间,要放入的对象大于可用内存空间,就会发生

Concurrent Mode Failure

,即并发垃圾回收失败了,我一边回收,你一边把对象放入老年代,内存都不够了。此时就会自动用

Serial Old

垃圾回收器替代CMS,就是直接强行把系统程序

Stop the World

,重新进行长时间的GC Roots追踪,标记出来全部垃圾对象,不允许新的对象产生。



③内存碎片问题

老年代的CMS采用

标记清理

算法(不是

标记整理

),每次都是标记出来垃圾对象,然后一次性回收掉,这样会导致大量的内存碎片产生,太多的内存碎片实际上会导致更加频繁的Full GC。

CMS有一个参数是

-XX:+UseCMSCompactAtFullCollection

,默认是打开的,意思是在Full GC之后要再次进行

Stop the World

,停止工作线程,然后进行碎片整理,就是把存活对象挪到一起,空出来大片连续内存空间,避免内存碎片。

还有一个参数是

-XX:CMSFullGCsBeforeCompaction

,这个意思是执行多少次Full GC之后再执行一次内存碎片整理的工作,默认是0,意思就是每次Full GC之后都会进行一次内存整理,存活对象都放在一起,然后空出来大片连续内存空间可供使用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LFVPZWhV-1581153515942)(/Users/zhangye/Library/Application Support/typora-user-images/image-20191023214004334.png)]

红圈处即浮动垃圾



版权声明:本文为weixin_44424668原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。