ZGC介绍
ZGC(The Z Garbage Collector)是JDK 11中推出的一款追求极致低延迟的实验性质的垃圾收集器,它曾经设计目标包括:java
- 停顿时间不超过10ms;
- 停顿时间不会随着堆的大小,或者活跃对象的大小而增长;
- 支持8MB~4TB级别的堆(将来支持16TB)。
当初,提出这个目标的时候,有不少人都以为设计者在吹牛逼。算法
但今天看来,这些“吹下的牛逼”都在一个个被实现。安全
基于最新的JDK15来看,“停顿时间不超过10ms”和“支持16TB的堆”这两个目标已经实现,而且官方明确指出JDK15中的ZGC再也不是实验性质的垃圾收集器,且建议投入生产了。微信
本文会从ZGC的设计思路出发,讲清楚为什么ZGC能在低延时场景中的应用中有着如此卓越的表现。并发
核心技术
多重映射
为了能更好的理解ZGC的内存管理,咱们先看一下这个例子:app
你在你爸爸妈妈眼中是儿子,在你女友眼中是男友。在全世界人面前就是最帅的人。你还有一个名字,但名字也只是你的一个代号,并非你本人。将这个关系画一张映射图表示:less
- 在你爸爸的眼中,你就是儿子;
- 在你女友的眼中,你就说男友;
- 站在全世界角度来看,你就说世界上最帅的人;
假如你的名字是全世界惟一的,经过“你的名字”、“你爸爸的儿子”、“你女友的男友”,“世界上最帅的人”最后定位到的都是你本人。性能
如今咱们再来看看ZGC的内存管理。测试
ZGC为了能高效、灵活地管理内存,实现了两级内存管理:虚拟内存和物理内存,而且实现了物理内存和虚拟内存的映射关系。这和操做系统中虚拟地址和物理地址设计思路基本一致。优化
当应用程序建立对象时,首先在堆空间申请一个虚拟地址,ZGC同时会为该对象在Marked0、Marked1和Remapped三个视图空间分别申请一个虚拟地址,且这三个虚拟地址对应同一个物理地址。
图中的Marked0、Marked1和Remapped三个视图是什么意思呢?
对照上面的例子,这三个视图分别对应的就是"你爸爸眼中",“你女友的眼中”,“全世界人眼中”。
而三个视图里面的地址,都是虚拟地址,对应的是“你爸爸眼中的儿子”,“你女友眼中的男友”......
最后,这些虚地址都能定位到一个物理地址,这个物理地址对应上面例子中的“你本人”。
用一段简单的Java代码表示就是这样的:
<div align="center" width="50%;"> 
在ZGC中这三个空间在同一时间点有且仅有一个空间有效。
为何这么设计呢?这就是ZGC的高明之处,利用虚拟空间换时间,这三个空间的切换是由垃圾回收的不一样阶段触发的,经过限定三个空间在同一时间点有且仅有一个空间有效高效的完成GC过程的并发操做,具体实现会后面讲ZGC并发处理算法的部分再详细描述。
染色指针
在讲ZGC并发处理算法以前,还须要补充一个知识点——染色指针。
咱们都知道,以前的垃圾收集器都是把GC信息(标记信息、GC分代年龄..)存在对象头的Mark Word里。举个例子:
若是某我的是个垃圾人,就在这我的的头上盖一个“垃圾”的章;若是这我的不是垃圾了,就把这我的头上的“垃圾”印章洗掉。
而ZGC是这样作的:
若是某我的是垃圾人。就在这我的的身份证信息里面标注这我的是个垃圾,之后无论这我的在哪刷身份证,别人都知道他是个垃圾人了。也许哪一天,这我的醒悟了再也不是垃圾人了,就把这我的身份证里面的“垃圾”标志去掉。
在这例子中,“这我的”就是一个对象,而“身份证”就是指向这个对象的指针。
ZGC将信息存储在指针中,这种技术有一个高大上的名字——染色指针(Colored Pointer)。
在64位的机器中,对象指针是64位的。
- ZGC使用64位地址空间的第0~43位存储对象地址,2^44 = 16TB,因此ZGC最大支持16TB的堆。
- 而第44~47位做为颜色标志位,Marked0、Marked1和Remapped表明三个视图标志位,Finalizable表示这个对象只能经过finalizer才能访问。
- 第48~63位固定为0没有利用。
读屏障
读屏障是JVM向应用代码插入一小段代码的技术。当应用线程从堆中读取对象引用时,就会执行这段代码。千万不要把这个读屏障和Java内存模型里面的读屏障搞混了,二者根本不是同一个东西,ZGC中的读屏障更像是一种AOP技术,在字节码层面或者编译代码层面给读操做增长一个额外的处理。
读屏障实例:
Object o = obj.FieldA // 从堆中读取对象引用,须要加入读屏障 <load barrier needed here> Object p = o // 无需加入读屏障,由于不是从堆中读取引用 o.dosomething() // 无需加入读屏障,由于不是从堆中读取引用 int i = obj.FieldB // 无需加入读屏障,由于不是对象引用
ZGC中读屏障的代码做用:
GC线程和应用线程是并发执行的,因此存在应用线程去A对象内部的引用所指向的对象B的时候,这个对象B正在被GC线程移动或者其余操做,加上读屏障以后,应用线程会去探测对象B是否被GC线程操做,而后等待操做完成再读取对象,确保数据的准确性。具体的探测和操做步骤以下:
这样会影响程序的性能吗?
会。据测试,最多百分之4的性能损耗。但这是ZGC并发转移的基础,为了下降STW,设计者认为这点牺牲是可接受的。
ZGC并发处理算法
ZGC并发处理算法利用全局空间视图的切换和对象地址视图的切换,结合SATB算法实现了高效的并发。
以上全部的铺垫,都是为了讲清楚ZGC的并发处理算法,在一些博文上,都说染色指针和读屏障是ZGC的核心,但都没有讲清楚二者是如何在算法里面被利用的,我认为,ZGC的并发处理算法才是ZGC的核心,染色指针和读屏障只不过是为算法服务而已。
ZGC的并发处理算法三个阶段的全局视图切换以下:
- 初始化阶段:ZGC初始化以后,整个内存空间的地址视图被设置为Remapped
- 标记阶段:当进入标记阶段时的视图转变为Marked0(如下皆简称M0)或者Marked1(如下皆简称M1)
- 转移阶段:从标记阶段结束进入转移阶段时的视图再次设置为Remapped
标记阶段
标记阶段全局视图切换到M0视图。由于应用程序和标记线程并发执行,那么对象的访问可能来自标记线程和应用程序线程。
在标记阶段结束以后,对象的地址视图要么是M0,要么是Remapped。
- 若是对象的地址视图是M0,说明对象是活跃的;
- 若是对象的地址视图是Remapped,说明对象是不活跃的,即对象所使用的内存能够被回收。
当标记阶段结束后,ZGC会把全部活跃对象的地址存到对象活跃信息表,活跃对象的地址视图都是M0。
转移阶段
转移阶段切换到Remapped视图。由于应用程序和转移线程也是并发执行,那么对象的访问可能来自转移线程和应用程序线程。
至此,ZGC的一个垃圾回收周期中,并发标记和并发转移就结束了。
为什么要设计M0和M1
咱们提到在标记阶段存在两个地址视图M0和M1,上面的算法过程显示只用到了一个地址视图,为何设计成两个?简单地说是为了区别前一次标记和当前标记。
ZGC是按照页面进行部份内存垃圾回收的,也就是说当对象所在的页面须要回收时,页面里面的对象须要被转移,若是页面不须要转移,页面里面的对象也就不须要转移。
如图,这个对象在第二次GC周期开始的时候,地址视图仍是M0。若是第二次GC的标记阶段还切到M0视图的话,就不能区分出对象是活跃的,仍是上一次垃圾回收标记过的。这个时候,第二次GC周期的标记阶段切到M1视图的话就能够区分了,此时这3个地址视图表明的含义是:
-
M1:本次垃圾回收中识别的活跃对象。
-
M0:前一次垃圾回收的标记阶段被标记过的活跃对象,对象在转移阶段未被转移,可是在本次垃圾回收中被识别为不活跃对象。
-
Remapped:前一次垃圾回收的转移阶段发生转移的对象或者是被应用程序线程访问的对象,可是在本次垃圾回收中被识别为不活跃对象。
如今,咱们能够回答“使用地址视图和染色指针有什么好处”这个问题了
使用地址视图和染色指针能够加快标记和转移的速度。之前的垃圾回收器经过修改对象头的标记位来标记GC信息,这是有内存存取访问的,而ZGC经过地址视图和染色指针技术,无需任何对象访问,只须要设置地址中对应的标志位便可。这就是ZGC在标记和转移阶段速度更快的缘由。
当GC信息再也不存储在对象头上时而存在引用指针上时,当肯定一个对象已经无用的时候,能够当即重用对应的内存空间,这是把GC信息放到对象头所作不到的。
ZGC步骤
ZGC采用的是标记-复制算法,标记、转移和重定位阶段几乎都是并发的,ZGC垃圾回收周期以下图所示:
ZGC只有三个STW阶段:初始标记,再标记,初始转移。
其中,初始标记和初始转移分别都只须要扫描全部GC Roots,其处理时间和GC Roots的数量成正比,通常状况耗时很是短;
再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎全部暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增长。与ZGC对比,G1的转移阶段彻底STW的,且停顿时间随存活对象的大小增长而增长。
ZGC的发展
ZGC诞生于JDK11,通过不断的完善,JDK15中的ZGC已经再也不是实验性质的了。
从只支持Linux/x64,到如今支持多平台;从不支持指针压缩,到支持压缩类指针.....
在JDK16,ZGC将支持并发线程栈扫描(Concurrent Thread Stack Scanning),根据SPECjbb2015测试结果,实现并发线程栈扫描以后,ZGC的STW时间又能下降一个数量级,停顿时间将进入毫秒时代。
ZGC已然是一款优秀的垃圾收集器了,它借鉴了Pauseless GC,也彷佛在朝着C4 GC的方向发展——引入分代思想。
Oracle的努力,让咱们开发者看到了商用级别的GC“飞入寻常百姓家”的但愿,随着JDK的发展,我相信在将来的某一天,JVM调优这种反人类的操做将不复存在,底层的GC会自适应各类状况自动优化。
ZGC确实是Java的最前沿的技术,但在G1都没有普及的今天,谈论ZGC彷佛为时过早。但也许咱们探讨的不是ZGC,而是ZGC背后的设计思路。
但愿你能有所收获!
谈谈生活
谈谈本身对疫情的见解。
个人见解是:注意安全,没必要过于担心
前段时间,由于我室友的公司所在的大楼的一个公司的一个员工去韩国核酸检测为阳性,因此我居家隔离了14天,从12月24号到1月6号。
被隔离的第一天,个人北京健康码就被标注了“居家观察”,社区工做人员第一时间就在门口装了摄像头,整个过程不容许出门,不容许和人接触。
这14天里,吃的东西都是点的外卖,为了避免直接接触,都是外卖放门口,等外卖小哥走了再开门取。
天天都会由社区人员上门来收取生活垃圾,而后集中处理。上午和下午都须要在社区服务群里面汇报本身的体温和情况,有什么须要和帮助也能够在群里说,社区人员也会立刻回应。
14天一共作了三次核酸检测,每次检测的时候都会给你发一个N95的口罩。最后一次作核酸检测那天北京-20度,为了避免让咱们感冒,还有专车接送,一批一批的送到指定点作核酸检测。
整个过程管控的很是细心,社区人员方方面面都想得很是周到。因此,在国家这种管控力度下,我相信疫情不会继续扩散的。
同时也但愿你们作好防御措施,保护好本身,欢欢喜喜回家过个年。做为打工人的我是很是想回家过年的。回家过年本就是多年的传承,就像候鸟南飞同样。固然特殊时期能够特殊对待,但条件容许的话仍是回家吧,看看家里的老人,见见各奔东西的朋友,一年就这几天而已。
写在最后
为了对每一篇发出去的文章负责,力求准确,我通常是参考官方文档和业界权威的书籍,有些时候,还须要看一些论文,看一部分源代码。而官方文档和论文通常都是英文,对于一个英语四级只考了456分的人来讲,很是艰难,整个过程都是谷歌翻译和有道词典陪伴着个人。由于一些专业术语翻译的不够准确,还须要英文和翻译对照慢慢理解。
但即便这样,也不免会有纰漏,若是你发现了,欢迎提出,我会对其修正。
你的正反馈对我来讲很是重要,点个赞,点个关注都是对我最大的支持!
若是你有什么想和我交流的,能够关注个人微信公众号“CoderW”,很是欢迎并感谢您的关注!
谢谢您的阅读,咱们下期再见!
参考资料
- https://mp.weixin.qq.com/s/ag5u2EPObx7bZr7hkcrOTg
- 《新一代垃圾回收器ZGC设计与实现》 </load></div>