探究JVM(七)手敲6000字!从论文角度剖析增量更新和原始快照-程序员宅基地

技术标签: JVM  jvm  算法  java  后端  数据结构  

引言:在JVM中,使用可达性分析算法来判断一个对象是否存活。在Serial和Parallel收集器中,可达性分析的过程是STW的,这意味着在标记的过程中,对象的引用关系没有发生改变,从GC Root开始扫描,可以得到全部存活的对象。但是在CMS,G1等垃圾收集器中,采用了并发标记的方式来遍历对象图,毫无疑问,这缩短了STW的时间,但是也带来了新的问题,而增量更新和原始快照就是用来解决这个问题的不同方式。

一 并发标记的问题

采用了并发标记,可以让用户线程和GC线程同时工作,但是也带来了新的问题。

1 浮动垃圾

在进行并发标记的时候,无法处理初始标记以后产生的对象,这些对象称为浮动垃圾,这些浮动垃圾如果过多,会导致堆没有空间可以存放这些对象,因为这时候还在进行GC,旧的对象没有被回收。但这相对来说还是可控的,可以适当调整触发GC的堆大小的阈值来尽量避免这种情况的发生。

2 对象消失

在并发标记的时候,用户能够操作对象,这就有可能使得引用关系发生修改,使得所有指向本来活的对象的引用全部消失,那按照可达性分析算法,此时对象会被判定为死亡,但是实际这个对象是存活的。由于这个问题相对来说比较严重,所以我们下文以说明如何解决对象消失问题为主。

上面是用户线程修改引用关系前后的对比图。虚线代表引用已经被删除。
当同时满足以下两个条件时,就会出现对象消失的情况。

1 从黑色对象到白色对象增加了新的引用关系。
2 灰色对象直接或间接删除了到这个白色对象的全部引用关系。

因为黑色对象是不能再被访问的,而灰色对象又删除了到这个白色对象的全部引用关系,所以就造成了该白色对象本来是存活的,但是因为在扫描路径中没有出现而被当成死亡对象。

二 如何解决对象消失

为了解决对象消失的问题,在JVM中有两种方式,分别是增量更新和原始快照。在CMS垃圾收集器中使用增量更新,在G1垃圾收集器中使用原始快照。

增量更新(Increment Update)

在上文我们提到两个条件同时满足,那么对象就会“消失”,所以我们只需要破坏其中一个条件即可。增量更新破坏的就是第一个条件。
增量更新的做法是把新增白色对象变为灰色对象并记录下引用修改,具体的是做法是利用记忆集和写后屏障技术,但是在这里传统的记忆集并不能满足CMS垃圾收集器对于引用修改追踪的要求。原因是这样的,在并发标记的过程中,有可能独立进行多次Young GC,这时候Card Table就会频繁被修改,导致原有的dirty card变成了clean card,这样就导致在并发标记过程中不能记录下完整的引用关系。

所以这时候JVM就引入了Mod-Union Table。Mod-Union Table本质上也是一个Byte数组,它是所有并发标记过程中发生的Change Card Table的合集,它的作用是在Young GC发生前检查Crad Table的某一位是否dirty,也就是这一位的数值是否变成1,如果变成1,就记录到Mod-Union Table中。Mod-Union Table和Card Table的位是一一对应的关系。
下面为CMS论文关于Set Mod-Union Table Bit的原文:
This invariant ismaintained by young-generation collections, which set the mod union bits for all cards dirty in the card table before scanning those dirty cards.
在每次Young GC扫描dirty card前都在Mod-Union Table中记录下dirty card,这样就保证了即使在后面的Change Card Table中, Card Table中元素的数值发生过改变,但是在Mod-Union Table对应元素的数值始终为1。

从正常角度来讲,Card Table能记录下所有到老年代的引用修改,但是实际的应用中,Card Table没有被用来跟踪新生代到老年代的引用修改,因为新生代的对象朝生夕灭,引用更新频繁,这样新生代基本全为dirty card,而处理这些dirty card也需要消耗资源,不如直接扫描新生代划算。在具体实现中,JVM用扫描整个新生代来代替处理Card Table中新生代到老年代引用关系的变化,而用Card Table和Mod-Union Table记录下老年代到老年代的引用修改,并在Remark阶段进行处理。
所以总的来说,用增量更新实现的最终标记阶段是通过 以下三个步骤来完成的。
1 重新扫描新生代。
2 重新扫描并发标记结束时老年代的Card Table记录下的对象。
3 重新扫描并发标记结束时老年代的Mod-Union Table记录下的对象。
步骤2和步骤3的具体做法是把dirty page里面的黑色对象中新增的灰色对象字段重新扫描一遍。

原始快照( Snapshot At The Beginning SATB)

上文讲述的是利用增量更新来解决CMS并发标记过程中出现的对象消失问题。而在G1垃圾收集器中则使用原始快照的方式来解决这个问题。
原始快照破坏的是第二个条件,在灰色对象到白色对象的直接引用或者间接引用关系修改前,会把这个引用记录下来,放进当前用户线程私有的SATB Queue中,最终汇集到一个全局的SATB Queue Set中,在最终标记阶段再扫描这些记录下来的引用记录。

上文我们提到增量更新使用到了写屏障技术,但是上文提到的写屏障只用到了写后屏障,在原始快照中运用到了写前屏障,因为要在引用被删除前把灰色对象到白色对象的关系记录下来。

1 写前屏障的具体实现

下面是G1论文中关于写屏障的伪代码和解释:

Below we show pseudocode for the marking write barrier for a write of the value in rY to offset FieldOffset in an object whose address is in rX. Its operation is explained below.
在这里插入图片描述
The actual pointer store [rX, FieldOffset] := rY would follow. The first two lines of the barrier skip the remainder if marking is not in progress; for many programs, this filters out a large majority of the dynamically executed barriers. Lines 3 and 4 load the value in the object field, and check whether it is null. It is only necessary to log non-null values. In many programs the majority of pointer writes are initializing writes to previously-null fields, so this further filtering is quite effective.

大致意思是SATB只记录发生在并发标记阶段的引用修改,并且要记录的这个引用的值不能为空,当以上两者都满足的时候,就把这个引用放到当前用户线程私有的SATB Queue中。

2 并发标记阶段对于SATB Queue 的处理

The satb enqueue operation adds the pointer value to the thread’s current marking buffer. As with remembered set buffers, if the enqueue fills the buffer, it then adds it to the global set of completed marking buffers. The concurrent marking thread checks the size of this set at regular intervals, interrupting its heap traversal to process filled buffers.
在并发标记阶段,有可能因为引用修改较为频繁,SATB Queue的大小超过了分配给线程的缓冲区域,这时候SATB Queue会被添加进Global SATB Queue Set中(下文用GSQS代替),再给当前用户线程换一个全新的SATB Queue供其记录引用。
当GSQS中的队列越来越多,整体大小也超过了阈值,那么并发标记阶段就会对GSQS中的引用进行处理,G1收集器使用GC线程把这些SATB Queue记录下的引用取出并压入标记栈(marking stack)中。此时并发标记阶段没有结束,GC线程会从标记栈中取出引用并扫描。

3 最终标记阶段对于SATB Queue 的处理

It is very simple: any unprocessed completed log buffers are processed as above, and the partially completed per-thread buffers are processed in the same way. This process is done in parallel, to guard against programs with many mutator threads with partially filled marking log buffers causing long pause times or parallel scaling issues.

到最终标记阶段的时候,暂停全部用户线程,并发处理每个用户线程的log buffers (即STAB Queue)中的引用。

三 解决浮动垃圾的策略

对于并发标记的问题,在上文我们主要关心的是“对象消失”的问题,该问题一旦发生,后果将非常严重,因为用户正在使用的对象是绝对不能被当成死亡对象回收的。而在下文,将讲述两者解决浮动垃圾问题的策略。
在CMS垃圾收集器中使用的是增量更新,增量更新追踪的是黑色对象到白色对象的直接引用,所以除了并发阶段的新增对象,在原始对象图中应该死亡的对象,增量更新算法都能够察觉到。
但是对于G1中的原始快照,记录的是直接引用或间接引用,此时在原始对象图中有一部分对象本来是死亡的,但是会被标记成存活的。另外原始快照也是无法回收并发阶段的新增对象。

在上述对象图中,对象A是死亡的,但是在原始快照算法中,它会被标记为存活对象。

四 总结

(1)原始快照的Remark阶段比增量更新快,因为增量更新要重新扫描新生代,原始快照只需要处理记录下的引用修改。
(2)对于对象消失问题,两者都能解决。
(3)原始快照的浮动垃圾比增量更新多。

五 参考资料

(1) CMS论文地址:http://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=708077E17B9BF4DEB8C8A850D34C8D60?doi=10.1.1.22.8915&rep=rep1&type=pdf
(2)G1论文地址:
https://dl.acm.org/doi/pdf/10.1145/1029873.1029879
(3)RednaxelaFX的博客:
https://hllvm-group.iteye.com/group/topic/44381

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Yao_ziwei/article/details/117781372

智能推荐

基于内核4.19版本的XFRM框架_linux的xfrm框架-程序员宅基地

文章浏览阅读794次,点赞2次,收藏5次。XFRM框架_linux的xfrm框架

织梦常用标签整理_织梦中什么页面用什么标签教学-程序员宅基地

文章浏览阅读774次。DedeCMS常用标签讲解笔记整理 今天我们主要将模板相关内容,在前面的几节课中已经基本介绍过模板标签的相关内容,大家可以下载天工开物老师的讲课记录:http://bbs.dedecms.com/132951.html,这次课程我们主要讲解模板具体的标签使用,并且结合一些实例来介绍这些标签。 先前课程介绍了,网站的模板就如同一件衣服,衣服的好坏直接决定了网站的好坏,很多网站一看界面_织梦中什么页面用什么标签教学

工作中如何编译开源工具(gdb)_gdb编译-程序员宅基地

文章浏览阅读2.5k次,点赞2次,收藏15次。编译是大部分工程师的烦恼,大家普遍喜欢去写业务代码。但我觉得基本的编译流程,我们还是需要掌握的,希望遇到相关问题,不要退缩,尝试去解决。天下文章一大抄,百度能解决我们90%的问题。_gdb编译

python简易爬虫v1.0-程序员宅基地

文章浏览阅读1.8k次,点赞4次,收藏6次。python简易爬虫v1.0作者:William Ma (the_CoderWM)进阶python的首秀,大部分童鞋肯定是做个简单的爬虫吧,众所周知,爬虫需要各种各样的第三方库,例如scrapy, bs4, requests, urllib3等等。此处,我们先从最简单的爬虫开始。首先,我们需要安装两个第三方库:requests和bs4。在cmd中输入以下代码:pip install requestspip install bs4等安装成功后,就可以进入pycharm来写爬虫了。爬

安装flask后vim出现:error detected while processing /home/zww/.vim/ftplugin/python/pyflakes.vim:line 28_freetorn.vim-程序员宅基地

文章浏览阅读2.6k次。解决方法:解决方法可以去github重新下载一个pyflakes.vim。执行如下命令git clone --recursive git://github.com/kevinw/pyflakes-vim.git然后进入git克降目录,./pyflakes-vim/ftplugin,通过如下命令将python目录下的所有文件复制到~/.vim/ftplugin目录下即可。cp -R ...._freetorn.vim

HIT CSAPP大作业:程序人生—Hello‘s P2P-程序员宅基地

文章浏览阅读210次,点赞7次,收藏3次。本文简述了hello.c源程序的预处理、编译、汇编、链接和运行的主要过程,以及hello程序的进程管理、存储管理与I/O管理,通过hello.c这一程序周期的描述,对程序的编译、加载、运行有了初步的了解。_hit csapp

随便推点

挑战安卓和iOS!刚刚,华为官宣鸿蒙手机版,P40搭载演示曝光!高管现场表态:我们准备好了...-程序员宅基地

文章浏览阅读472次。点击上方 "程序员小乐"关注,星标或置顶一起成长后台回复“大礼包”有惊喜礼包!关注订阅号「程序员小乐」,收看更多精彩内容每日英文Sometimes you play a..._挑战安卓和ios!华为官宣鸿蒙手机版,p40搭载演示曝光!高管表态:我们准备好了

精选了20个Python实战项目(附源码),拿走就用!-程序员宅基地

文章浏览阅读3.8w次,点赞107次,收藏993次。点击上方“Python爬虫与数据挖掘”,进行关注回复“书籍”即可获赠Python从入门到进阶共10本电子书今日鸡汤昔闻洞庭水,今上岳阳楼。大家好,我是小F。Python是目前最好的编程语言之一。由于其可读性和对初学者的友好性,已被广泛使用。那么要想学会并掌握Python,可以实战的练习项目是必不可少的。接下来,我将给大家介绍20个非常实用的Python项目,帮助大家更好的..._python项目

android在线图标生成工具,图标在线生成工具Android Asset Studio的使用-程序员宅基地

文章浏览阅读1.3k次。在网站的导航资源里看到了一个非常好用的东西:Android Asset Studio,可以在线生成各种图标。之前一直在用一个叫做Android Icon Creator的插件,可以直接在Android Studio的插件里搜索,这个工具的优点是可以生成适应各种分辨率的一套图标,有好几种风格的图标资源,遗憾的是虽然有很多套图标风格,毕竟是有限的。Android Asset Studio可以自己选择其..._在线 android 图标

android 无限轮播的广告位_轮播广告位-程序员宅基地

文章浏览阅读514次。无限轮播广告位没有录屏,将就将就着看,效果就是这样主要代码KsBanner.java/** * 广告位 * * Created by on 2016/12/20. */public class KsBanner extends FrameLayout implements ViewPager.OnPageChangeListener { private List

echart省会流向图(物流运输、地图)_java+echart地图+物流跟踪-程序员宅基地

文章浏览阅读2.2k次,点赞2次,收藏6次。继续上次的echart博客,由于省会流向图是从echart画廊中直接取来的。所以直接上代码<!DOCTYPE html><html><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /&_java+echart地图+物流跟踪

Ceph源码解析:读写流程_ceph 发送数据到其他副本的源码-程序员宅基地

文章浏览阅读1.4k次。一、OSD模块简介1.1 消息封装:在OSD上发送和接收信息。cluster_messenger -与其它OSDs和monitors沟通client_messenger -与客户端沟通1.2 消息调度:Dispatcher类,主要负责消息分类1.3 工作队列:1.3.1 OpWQ: 处理ops(从客户端)和sub ops(从其他的OSD)。运行在op_tp线程池。1...._ceph 发送数据到其他副本的源码