再回首CMS垃圾回收

前言

之前学习JVM垃圾回收时,主要是过了一遍垃圾收集算法,比如复制算法,标记-清除算法,标记-整理算法,在此基础上可以增加分代,每代采取不同的回收算法,以提高整体的分配和回收效率。然后过了一遍JVM中的垃圾收集器,比如Serial、Parallel Scavenge、Parallel New、CMS、G1等。

自认为垃圾收集就是根据GC Root标记所有可达的对象,然后把所有没有标记的对象清除就ok了。是不是很简单。事实上垃圾收集也就是这么一回事,但是很多时候说起来简单,做起来却会出现很多问题。这篇文章就是记录我对CMS垃圾收集器的一些疑问并学习的过程。

首先看一下CMS的整体流程(具体每个流程的详情就自行了解吧)

CMS流程

如何进行标记?

最近在看Golang的GC算法实现,里面用到了三色标记法,但是在我的知识库中对三色标记法有这个概念,是的,我只知道这个概念,不知道三色标记法是怎么一个流程,也不知道三色标记法在GC中怎么与运行的。于是就开始了我的探险之旅。

在搜索了一下三色标记法(具体可以看一下文末参考文档中三色标记法与读写屏障了解详情)后,发现现代追踪式(可达性分析)的垃圾回收器几乎都借鉴了三色标记的算法思想,CMS垃圾收集器也不例外。

GC Root有哪些?

我们知道怎么进行标记了,但最初标记的时候需要一些根据才行啊,这些根据就是我们说的GC Root。GC Root有哪些?网上有很多的答案,我的理解就是

  • 当前活跃调用栈中的指向对象的引用
  • 一些不会发生改变的数据所指向的引用

这里我使用的是引用,而不是对象,因为R大是这样说的(具体的问题见参考文档java的gc为什么要分代?

所谓“GC roots”,或者说tracing GC的“根集合”,就是一组必须活跃的引用
例如说,这些引用可能包括:

  • 所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
  • VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。
  • JNI handles,包括global handles和local handles
  • (看情况)所有当前被加载的Java类
  • (看情况)Java类的引用类型静态变量
  • (看情况)Java类的运行时常量池里的引用类型常量(String或Class类型)
  • (看情况)String常量池(StringTable)里的引用

注意,是一组必须活跃的引用,不是对象。

现在知道了GC Root,但是我们都知道有分代的概念,新生代的gc和老年的代的gc回收的区域是不一样,那么这里的GC Root是不是应该不一样呢?肯定是不一样的。

首先看一下新生代的GC

新生代的区域一般都比较小,而且对象的存活率都比较低,所以按照前面说的GC Root在新生代的区域扫描就行了。但是会有一个问题?老年代存在引用新生代对象的可能啊?如果只扫描新生代的区域,会漏掉被老年代引用的对象,这些对象就会被清除掉,这是不允许的。

如果这样的话,那是不是扫描一下老年代的对象,看是否引用新生代的对象是不是就ok了?嗯这么做肯定是ok的,但是老年代一般很大,而且存活的对象很多,会导致扫描占用很长的时间。那这个问题如何解?JVM是如何避免Minor GC时扫描全堆的?

经过统计信息显示,老年代持有新生代对象引用的情况不足1%,根据这一特性JVM引入了卡表(card table)来实现这一目的。如下图所示:

CardTable

卡表的具体策略是将老年代的空间分成大小为512B的若干张卡(card)。卡表本身是单字节数组,数组中的每个元素对应着一张卡,当发生老年代引用新生代时,虚拟机将该卡对应的卡表元素设置为适当的值。如上图所示,卡表3被标记为脏(卡表还有另外的作用,标识并发标记阶段哪些块被修改过),之后Minor GC时通过扫描卡表就可以很快的识别哪些卡中存在老年代指向新生代的引用。这样虚拟机通过空间换时间的方式,避免了全堆扫描。

所以新年代GC的GC Root包含2部分

  • 新生代中满足GC Root定义的对象
  • 卡表中老年代引用新生代的对象

老年代的GC

前面我们说了新生代的gc,我们已同样的思路来看看老年代的gc,老年代的GC Root如何来标记呢?只扫描老年代可以吗?当然是不行的,因为新生代中也可能存在老年代对象的引用,好在新生代并不大,所以老年代GC的时候还需要扫描一遍新生代。

新生代GC的Root

所以老年代GC的GC Root包含2部分

  • 老生代中满足GC Root定义的对象,如图节点1;
  • 标记年轻代中活着的对象引用到的老年代的对象(指的是年轻代中还存活的引用类型对象,引用指向老年代中的对象)如图节点2、3;

并发标记的好坏?

标记作为垃圾回收的第一步,现在知道如何进行标记,接下来就是遍历这些对象,将所有未标记的对象清理就完成GC了。

然而事实上并没有这么简单,如果标记的时候是STW的,那就是这么简单,但是如果标记过程都STW会造成暂停时间过长,给人的感觉就是系统一卡一卡的。

于是就把标记的过程改成并发的进行,也就是CMS中并发标记的过程,然而这就是一切复杂问题的源头。虽然并发标记提升了标记的效率,但是因此却引发了一系列的问题。

因为并发标记时,gc线程和用户线程是并行的,所以在这个过程中会出现下面的情况(需要了解三色标记法与读写屏障):

  • 新生代晋升到老年代
  • 黑色对象取消对灰色对象的引用(浮动垃圾)
  • 黑色对象新增对白色对象的引用(漏标)

其实在三色标记法与读写屏障文中已经给出了解决方法--添加读写屏障

  • 写屏障 + SATB
  • 写屏障 + 增量更新
  • 读屏障(Load Barrier)

在CMS并发标记阶段,使用 写屏障 + 增量更新 的方法,将上面出现的情况标记为dirty,这样最后再遍历处理一下Dirty集合中的对象就ok了

标记为dirty

重新标记阶段为什么还要扫描新生代?

因为存在跨代引用,但是前面说过这种情况,通过读写屏障的方式标记这些为dirty,只需要扫描老年代和dirty集合就行了啊?哎,看来我还是太年轻,如果只扫描老年代和dirty集合会漏掉一部分,会是哪部分呢?老年代和dirty集合还没有覆盖完吗?

是的,老年代和dirty集合的确没有覆盖完。我们来分析一下。老年代中经过初始标记和并发标记后,只有黑色对象和白色对象了,黑色的就是要留下的,白色的就是要被清除的。黑色对象是怎么来的?根据GC Root找到的,所以只要并发标记过程中,GC Root不发生变化,黑色对象就没有问题(不会漏标),如果在并发标记过程中GC Root发生了变化呢?

当并发标记过程中GC Root增加了,并且这个GC Root还引用了老年代中的对象,此时如果只扫描老年代和dirty集合就会漏标。因此重新标记阶段仍然需要扫描新生代。

预处理阶段都干了啥?

预处理阶段其实有2部分:

  • 预清理阶段
  • 可终止的预处理

这个阶段的目的都是为了减轻后面的重新标记的压力,提前做一点重新标记阶段的工作。一般CMS的GC耗时80%都在remark阶段,所以预处理阶段也是为了减少remark阶段的STW时间。

重新标记阶段需要做一下工作:

  1. 遍历新生代对象,重新标记
  2. 根据GC Roots,重新标记
  3. 遍历老年代的Dirty Card,重新标记(这里的Dirty Card大部分已经在clean阶段处理过)

遍历新生代对象时,可能很多对象已经是不可达了,但是还是需要扫描。遍历Dirty Card做处理。

这2部分其实就是预处理阶段帮助重新标记减轻压力的地方

  • 预清理阶段和可终止的预处理都会扫描Dirty Card做处理
  • 可终止的预处理,尽量进行一次ygc,让不可达的对象被回收掉,remark阶段遍历新生代的对象成本小一点

具体这个阶段的详情见参考文档图解CMS垃圾回收机制,你值得拥有

参考文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,930评论 1 332
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,642评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,928评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,639评论 0 193
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,806评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,698评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,139评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,864评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,485评论 0 230
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,955评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,355评论 1 243
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,811评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,300评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,840评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,464评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,577评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,628评论 2 249