ZGC介绍
ZGC(The Z Garbage Collector)是JDK 11中推出的一款谋求极致低提早的试验性质的垃圾收集器,它已经设计指标包含:
- 进展工夫不超过10ms;
- 进展工夫不会随着堆的大小,或者沉闷对象的大小而减少;
- 反对8MB~4TB级别的堆(将来反对16TB)。
当初,提出这个指标的时候,有很多人都感觉设计者在吹牛逼。
但明天看来,这些“吹下的牛逼”都在一个个被实现。
基于最新的JDK15来看,“进展工夫不超过10ms”和“反对16TB的堆”这两个指标曾经实现,并且官网明确指出JDK15中的ZGC不再是试验性质的垃圾收集器,且倡议投入生产了。
ZGC曾经熟了,面试题还会远吗?
本文会从ZGC的设计思路登程,讲清楚为何ZGC能在低延时场景中的利用中有着如此卓越的体现。
核心技术
多重映射
为了能更好的了解ZGC的内存治理,咱们先看一下这个例子:
你在你爸爸妈妈眼中是儿子,在你女朋友眼中是男朋友。在全世界人背后就是最帅的人。你还有一个名字,但名字也只是你的一个代号,并不是你自己。将这个关系画一张映射图示意:
- 在你爸爸的眼中,你就是儿子;
- 在你女朋友的眼中,你就说男朋友;
- 站在全世界角度来看,你就说世界上最帅的人;
如果你的名字是全世界惟一的,通过“你的名字”、“你爸爸的儿子”、“你女朋友的男朋友”,“世界上最帅的人”最初定位到的都是你自己。
当初咱们再来看看ZGC的内存治理。
ZGC为了能高效、灵便地治理内存,实现了两级内存治理:虚拟内存和物理内存,并且实现了物理内存和虚拟内存的映射关系。这和操作系统中虚拟地址和物理地址设计思路基本一致。
当应用程序创建对象时,首先在堆空间申请一个虚拟地址,ZGC同时会为该对象在Marked0、Marked1和Remapped三个视图空间别离申请一个虚拟地址,且这三个虚拟地址对应同一个物理地址。
图中的Marked0、Marked1和Remapped三个视图是什么意思呢?
对照下面的例子,这三个视图别离对应的就是"你爸爸眼中",“你女朋友的眼中”,“全世界人眼中”。
而三个视图外面的地址,都是虚拟地址,对应的是“你爸爸眼中的儿子”,“你女朋友眼中的男朋友”......
最初,这些虚地址都能映射到同一个物理地址,这个物理地址对应下面例子中的“你自己”。
用一段简略的Java代码示意这种关系:
在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背地的设计思路。
心愿你能有所播种!
写在最初
为了对每一篇收回去的文章负责,力求精确,我个别是参考官网文档和业界权威的书籍,有些时候,还须要看一些论文,看一部分源代码。而官网文档和论文个别都是英文,对于一个英语四级只考了456分的人来说,十分艰巨,整个过程都是谷歌翻译和有道词典陪伴着我的。因为一些专业术语翻译的不够精确,还须要英文和翻译对照缓缓了解。
但即便这样,也难免会有纰漏,如果你发现了,欢送提出,我会对其修改。
你的正反馈对我来说十分重要,点个赞,点个再看,点个关注都是对我最大的反对!
谢谢您的浏览,咱们下期再见!