一、ZGC简介和性能
G1的目标是在可控的停顿时间内完成垃圾回收,所以进行了分区设计,在回收时采用部分内存回收(在YGC时会回收所有新生代分区,在混合回收时会回收所有的新生代分区和部分老生代分区),支持的内存也可以达到几十个GB或者上百个GB。为了进行部分回收,G1实现了RSet管理对象的引用关系。基于G1设计上的特点,导致存在以下问题:
- 1.停顿时间过长,通常G1的停顿时间要达到几十到几百毫秒;这个数字其实已经非常小了,但是我们知道垃圾回收发生导致应用程序在这几十或者几百毫秒中不能提供服务,在某些场景中,特别是对用户体验有较高要求的情况下不能满足实际需求。
- 2.内存利用率不高,通常引用关系的处理需要额外消耗内存,一般占整个内存的1%~20%左右。
- 3.支持的内存空间有限,不适用于超大内存的系统,特别是在内存容量高于100GB的系统中,会因内存过大而导致停顿时间增长。
ZGC作为新一代的垃圾回收器,在设计之初就定义了三大目标:
- 1.支持TB级内存
- 2.停顿时间控制在10ms之内
- 3.对程序吞吐量影响小于15%。
![](https://img.haomeiwen.com/i13587608/5ad35d8f21661612.png)
实际上目前ZGC已经满足设计之初定义的目标,最大支持4TB堆空间,依据实际测试的情况来看,停顿时间通常都在10ms以下,并且垃圾回收所引起的暂停时间并不会随着内存的增大而延长。
ZGC如何设计以达成目标?
简单地说,就是ZGC把一切能并发处理的工作都并发执行。
ZGC是在G1的基础上发展起来的,我们知道G1中实现了并发标记,所以标记已经不会再影响停顿时间了。
G1中的停顿时间主要来自垃圾回收(YGC和混合回收)阶段中的复制算法,在复制算法中,需要把对象转移到新的空间中,并且更新其他对象到这个对象的引用。实际中对象的转移涉及内存的分配和对象成员变量的复制,而对象成员变量的复制是非常耗时的。
在G1中对象的转移都是在STW中并行执行的,而ZGC就是把对象的转移也并发执行,从而满足停顿时间在10ms以下。
我们看到G1只有在MARKING的时候,是并发的。而ZGC 在对象的复制和压缩,复制集的选择,等很多方面都改成了并发(和应用线程同时进行)。这就是它STW时间如此之短的秘诀。
JDK 16 发布后,GC 暂停时间已经缩小到 1 ms 以内,并且时间复杂度是 o(1),这也就是说 GC 停顿时间是一个固定值了,并不会受堆内存大小影响。
ZGC 有 3 个重要特性:
- 暂停时间不会超过 10 ms。
- 最大支持 16TB 的大堆(支持堆范围为8MB~4TB级别(未来支持16TB)),最小支持 8MB 的小堆。
- 跟 G1 相比,对应用程序吞吐量的影响小于 15 %。
![](https://img.haomeiwen.com/i13587608/a984e8f4058a10d7.png)
1、内存多重映射
内存多重映射,就是使用 mmap 把不同的虚拟内存地址映射到同一个物理内存地址上。如下图:
![](https://img.haomeiwen.com/i13587608/1cf690b9c807da66.png)
ZGC 为了更灵活高效地管理内存,使用了内存多重映射,把同一块儿物理内存映射为 Marked0、Marked1 和 Remapped 三个虚拟内存。
当应用程序创建对象时,会在堆上申请一个虚拟地址,这时 ZGC 会为这个对象在 Marked0、Marked1 和 Remapped 这三个视图空间分别申请一个虚拟地址,这三个虚拟地址映射到同一个物理地址。
Marked0、Marked1 和 Remapped 这三个虚拟内存作为 ZGC 的三个视图空间,在同一个时间点内只能有一个有效。ZGC 就是通过这三个视图空间的切换,来完成并发的垃圾回收。
2、染色指针
2.1 三色标记回顾
我们知道 G1 垃圾收集器使用了三色标记,这里先做一个回顾。下面是一个三色标记过程中的对象引用示例图:
![](https://img.haomeiwen.com/i13587608/3ac876434a10dbd7.png)
总共有三种颜色,说明如下:
- 白色:本对象还没有被标记线程访问过。
- 灰色:本对象已经被访问过,但是本对象引用的其他对象还没有被全部访问。
- 黑色:本对象已经被访问过,并且本对象引用的其他对象也都被访问过了。
三色标记的过程如下:
- 1.初始阶段,所有对象都是白色。
- 2.将 GC Roots 直接引用的对象标记为灰色。
- 3.处理灰色对象,把当前灰色对象引用的所有对象都变成灰色,之后将当前灰色对象变成黑色。
- 4.重复步骤 3,直到不存在灰色对象为止。
三色标记结束后,白色对象就是没有被引用的对象(比如上图中的 H 和 G),可以被回收了。
2.2 染色指针
ZGC 出现之前, GC 信息保存在对象头的 Mark Word 中。比如 64 位的 JVM,对象头的 Mark Word 中保存的信息如下图:
![](https://img.haomeiwen.com/i13587608/d744a5bd1c181575.png)
前 62位保存了 GC 信息,最后两位保存了锁标志。
ZGC 的一大创举是将 GC 信息保存在了染色指针上。染色指针是一种将少量信息直接存储在指针上的技术。在 64 位 JVM 中,对象指针是 64 位,如下图:
![](https://img.haomeiwen.com/i13587608/6b447e452b8b2129.png)
在这个 64 位的指针上,高 16 位都是 0,暂时不用来寻址。剩下的 48 位支持的内存可以达到 256 TB(2 ^48),这可以满足多数大型服务器的需要了。不过 ZGC 并没有把 48 位都用来保存对象信息,而是用高 4 位保存了四个标志位,这样 ZGC 可以管理的最大内存可以达到 16 TB(2 ^ 44)。
通过这四个标志位,JVM 可以从指针上直接看到对象的三色标记状态(Marked0、Marked1)、是否进入了重分配集(Remapped)、是否需要通过 finalize 方法来访问到(Finalizable)。
无需进行对象访问就可以获得 GC 信息,这大大提高了 GC 效率。
- Marked0和Marked1是用于标记对象辅助GC的。他们俩是交替使用的,如第一轮GC使用M0,第二轮GC使用M1做标记
- Remapped对象重定位时使用,如果一个指针处于Remapped那么说明这个指针已经重定位了,可以直接使用,反之,说明指针指向的是对象的老地址,需要重新定位找到新地址。
- Marked0、Marked1、Remapped三者是互斥的,只能有一位为1。如100,我们称为M0视图;010我们称为M1视图;001我们称为Remapped视图。后面为了方便介绍,如果一个指针是M0视图,我们就认为指针是绿色,如果是M1视图,是红色,Remapped视图是蓝色。
3、内存布局
首先我们回顾一下 G1 垃圾收集器的内存布局。G1把整个堆分成了大小相同的 region,每个堆大约可以有 2048 个region,每个 region 大小为 1~32 MB (必须是 2 的次方)。如下图:
![](https://img.haomeiwen.com/i13587608/e2a36b0580e94764.png)
跟 G1 类似,ZGC 的堆内存也是基于 Region 来分布,不过 ZGC 是不区分新生代老年代的。不同的是,ZGC 的 Region 支持动态地创建和销毁,并且 Region 的大小不是固定的,包括三种类型的 Region :
- 小型Region(Small Region):容量固定为2MB, 用于放置小于256KB的小对象。
- 中型Region(Medium Region):容量固定为32MB, 用于放置大于等于256KB但小于4MB的对象。
- 大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍, 用于放置4MB或以上的大对象。 每个大型Region中只会存放一个大对象, 这也预示着虽然名字叫作“大型Region”, 但它的实际容量完全有可能小于中型Region, 最小容量可低至4MB。此外,大型Region之所以只放一个对象,是因为复制大型对象的代价比较高昂,而如果一个Region中只有一个大型的对象,那么回收时只需要清除整个Region即可,不在需要做复制转移。
4、读屏障
读屏障类似于 Spring AOP 的前置增强,是 JVM 向应用代码中插入一小段代码,当应用线程从堆中读取对象的引用时,会先执行这段代码。注意:只有从堆内存中读取对象的引用时,才会执行这个代码。下面代码只有第一行需要加入读屏障。
Object o = obj.FieldA
Object p = o //不是从堆中读取引用
o.dosomething() //不是从堆中读取引用
int i = obj.FieldB //不是引用类型
读屏障在解释执行时通过 load 相关的字节码指令加载数据。作用是在对象标记和转移过程中,判断对象的引用地址是否满足条件,并作出相应动作。如下图:
![](https://img.haomeiwen.com/i13587608/0d9d556dbdb2f6ed.png)
读屏障会对应用程序的性能有一定影响,据测试,对性能的最高影响达到 4%,但提高了 GC 并发能力,降低了 STW。
二、ZGC流程介绍
并发垃圾回收算法实际上是以复制算法为基础,增加了并发处理。我们先回顾一下复制算法,它可以概括为3个阶段,分别为标记(mark)、转移(relocate)和重定位(remap)。这3个阶段分别完成的功能是:
- 标记:从根集合出发,标记活跃对象;此时内存中存在活跃对象和已死亡对象。
- 转移:把活跃对象转移(复制)到新的内存上,原来的内存空间可以回收。
- 重定位:因为对象的内存地址发生了变化,所以所有指向对象老地址的指针都要调整到对象新的地址上。
2.1 ZGC中的转移
首先我们回顾一下复制算法和标记整理算法。
在复制算法中,会有俩块内存空间,空间A和空间B,清理步骤如下:
- 1、将空间A的对象复制到空间B后
-
2、清空空间A完成垃圾回收。
在标记整理算法中,只在同一块内存空间中完全全部操作。
- 1、将所有存活的对象往内存的一边进行规整
-
2、清理掉端边界以外的内存
而在ZGC中的转移,有可能是复制算法,也有可能是标记整理算法。这取决于是否还有空余页,如果有的话就使用复制算法,如果已经没有多余的页,就使用标记整理算法,在一个页中完成垃圾回收。
因此,对于转移的定义是:对象的复制或移动(标记整理算法)
2.2 ZGC中的重定位
在复制对象后,对象的内存地址发生了变化,所以所有指向对象老地址的指针都需要调整到对象的新地址上,这个过程称为重定位。
比方说,我们堆中有一个对象,此时指针为蓝色(Remapped视图)
![](https://img.haomeiwen.com/i13587608/c1f00a3fcbc7f8c8.png)
在标记阶段,因为该对象是可达的活跃对象,因此指针会被标记为绿色(M0视图)。然后在转移阶段,绿色的对象会被转移到另外一个页内存中。
![](https://img.haomeiwen.com/i13587608/47d6403dcd9192ca.png)
原有的空间将会释放:
![](https://img.haomeiwen.com/i13587608/6e4fcb60e8961a96.png)
转移结束后,变量u持有的指针还是指向原来的老地址。但因为此时的指针不是处于Remapped视图。因此,当应用程序线程读取到这个指针时,会去一个转发表中找到对象的最新地址,然后修正原来的指针。(转发表可以理解成一个Map,可以根据老地址找到新的地址)
![](https://img.haomeiwen.com/i13587608/5b12a4d021748490.png)
最终,指针重新变回了Remapped视图。
以上演示的是惰性对象重定位。这也是为什么ZGC在垃圾回收阶段不用STW的原因。而G1在筛选回收阶段会进行STW,就是因为G1没有实现对象重定位,如果不STW,应用程序线程就可能对老对象地址进行操作,从而造成不可预知的错误。
从细节角度可以分为如下步骤
![](https://img.haomeiwen.com/i13587608/021186764635622f.png)
- 1、初始标记,从根集合出发,找出根集合直接引用的活跃对象,并入栈;该步需要STW。
- 2、并发标记,根据初始标记找到的根对象,使用深度优先遍历对象的成员变量进行标记;并发标记需要解决标记过程中引用关系变化导致的漏标记问题
- 3、再标记和非强根并行标记,在并发标记结束后尝试终结标记动作,理论上并发标记结束后所有待标记的对象会全部完成,但是因为GC工作线程和应用程序线程是并发运行,所以可能存在GC工作线程执行结束标记时,应用程序线程又有新的引用关系变化导致漏标记,所以这一步先判断是否真的结束了对象的标记,如果没有结束就还会启动并行标记,所以这一步需要STW。另外,在该步中,还会对非强根(软应用,虚引用等)进行并行标记。
- 4、并发处理非强引用和非强根并发标记
- 5、重置转移集合中的页面,实际上第一次垃圾回收时无须处理这一步。
- 6、回收无效的页面,实际上在内存充足的情况下不会触发这一步。
- 7、并发选择对象的转移集合,转移集合中就是待回收的页面。
- 8、并发初始化转移集合中的每个页面,在后续重定位(也称为Remap)时需要的对象转移表(Forward Table)就是在这一步初始化的。
- 9、转移根对象引用的对象,该步需要STW。
- 10、并发转移,把对象移动到新的页面中,这样对象所在的老的页面中所有活跃对象都被转移了,页面就可以被回收重用。
![](https://img.haomeiwen.com/i13587608/5e6a7180154b89bb.png)
![](https://img.haomeiwen.com/i13587608/81ba4f5724e3f1a3.png)
上述3个蓝线代表需要STW, 灰线代表可以并发运作。
- 初始标记:STW,标记所有GC Roots直接引用的对象
- 并发标记:顺着GC Roots直接引用的对象,往下扫描。扫描到的对象颜色指针M0会被设置为1。剩下的没被扫描到的对象,都是Remapped。
- 再标记(重新标记):(STW,同G1)还会处理一些非强根,解决并发标记阶段出现的漏标问题。
- 并发-转移前的准备(上面的4-8步,最核心的是引用处理,非强根清理,转移集(relocation set)的选择)
- 初始转移:STW,转移初始标记阶段标记的GC roots直接引用的对象,同时会做对象的重定位
- 并发转移:对并发标记阶段标记的存活对象进行转移。在这个阶段,对象重定位,采用的是一种惰性重定位,即当有用户线程访问了对象老地址时,才进行对象重定位,将指针修改为新地址的指针
并发算法中明确地提到重定位阶段,但上面的步骤中并没有体现。在ZGC中并没有明确这一步,重定位实际上被合并到标记阶段中,即在标记的时候如果发现对象引用到老的地址,这时会先完成重定位更新对象的引用关系,然后再标记对象。所以实质上ZGC的并发垃圾回收中还是包含了重定位这一阶段,只不过重定位和标记阶段复用了。
关于漏标问题可以看这篇文章:https://www.cnblogs.com/hongdada/p/14578950.html
下面通过画图介绍初始标记、并发标记、初始转移、并发转移这四个步骤的流程:
![](https://img.haomeiwen.com/i13587608/7978cfcfb52973a1.png)
上图表示的是一个页中的对象分布情况,其中有一个GC Roots根直接引用的对象,还有一个垃圾对象(左下角那个指针没有被任何变量持有的对象)。
接下来,我们做初始标记,标记GC Roots根直接引用的对象。被标记的对象指针将变为M0视图(绿色)
![](https://img.haomeiwen.com/i13587608/53b6e2b9bbf28e55.png)
接下来进入并发标记阶段,顺着GC roots根的引用,标记所有可达的对象:
![](https://img.haomeiwen.com/i13587608/4cf1e9af1de1150a.png)
跳过重新标记和并发转移准备,进入初始转移阶段,初始转移阶段会转移初始标记阶段标记的对象,也就是那些GC roots直接引用的对象,并且会完成对象的重定位。
![](https://img.haomeiwen.com/i13587608/eeea3e0946fa32c4.png)
接下来进入到并发转移阶段,该阶段会转移并发标记阶段标记的对象,但是不会完成对象的重定向(转移后因为没有做对象的重定位,原来的绿色指针还在,及时它指向的空间已经被释放了。如果此时有线程访问了这些指针,就会触发我们前面说的惰性重定位)
在并发转移阶段后,页内存1中存活的对象都会被标记成绿色(M0视图),所以如果还有蓝色对象的话,那这些对象就都是垃圾对象。
![](https://img.haomeiwen.com/i13587608/65ffeb6e77658e82.png)
在并发转移结束后,还有不少变量持有旧的指针,如果要修正这些指针的话,就需要重新遍历所有变量,找到那些持有旧指针的变量然后修正。但因为由惰性重定位的原因,因此这个过程并不急切,因此ZGC将这个阶段巧妙的放到了下一次GC时的并发标记阶段了(因为并发标记也会遍历所有变量,这样就只需要遍历一次)。这个阶段被称为并发重映射。
在下一次GC,做可达性分析时,存活对象指针标记为M1。而如果一个遇到绿色的指针,说明这是上一次GC阶段未重定位的指针,此时会完成对象的重定位。因此,M0和M1其实是交替使用的,目的就是为了区分相邻俩次GC的标志。
三、ZGC流程详解
ZGC 使用内存多重映射技术,把物理内存映射为 Marked0、Marked1 和 Remapped 三个地址视图,利用地址视图的切换,ZGC 实现了高效的并发收集。
ZGC 的垃圾收集过程包括标记、转移和重定位三个阶段。如下图:
![](https://img.haomeiwen.com/i13587608/935e5e37c55cbeef.png)
ZGC 初始化后,整个内存空间的地址视图被设置为 Remapped。
3.1 初始标记
从 GC Roots 出发,找出 GC Roots 直接引用的对象,放入活跃对象集合,这个过程需要 STW,不过 STW 的时间跟 GC Roots 数量成正比,耗时比较短。
3.2 并发标记
并发标记过程中,GC 线程和 Java 应用线程会并行运行。这个过程需要注意下面几点:
- GC 标记线程访问对象时,如果对象地址视图是 Remapped,就把对象地址视图切换到 Marked0,如果对象地址视图已经是 Marked0,说明已经被其他标记线程访问过了,跳过不处理。
- 标记过程中Java 应用线程新创建的对象会直接进入 Marked0 视图。
- 标记过程中Java 应用线程访问对象时,如果对象的地址视图是 Remapped,就把对象地址视图切换到 Marked0,可以参考前面讲的读屏障。
- 标记结束后,如果对象地址视图是 Marked0,那就是活跃的,如果对象地址视图是 Remapped,那就是不活跃的。
这里采用两个视图是为了区分前一次标记和这一次标记。如果这次标记的视图是 Marked0,那下一次并发标记就会把视图切换到 Marked1。这样做可以配合 ZGC 按照页回收垃圾的做法。如下图:
![](https://img.haomeiwen.com/i13587608/81f73f36386363d3.png)
第二次标记的时候,如果还是切换到 Marked0,那么 2 这个对象区分不出是活跃的还是上次标记过的。如果第二次标记切换到 Marked1,就可以区分出了。
这时 Marked0 这个视图的对象就是上次标记过程被标记过活跃,转移的时候没有被转移,但这次标记没有被标记为活跃的对象。Marked1 视图的对象是这次标记被标记为活跃的对象。Remapped 视图的对象是上次垃圾回收发生转移或者是被 Java 应用线程访问过,本次垃圾回收中被标记为不活跃的对象。
3.3 再标记
并发标记阶段 GC 线程和 Java 应用线程并发执行,标记过程中可能会有引用关系发生变化而导致的漏标记问题。再标记阶段重新标记并发标记阶段发生变化的对象,还会对非强引用(软应用,虚引用等)进行并行标记。
这个阶段需要 STW,但是需要标记的对象少,耗时很短。
3.4 初始转移
转移就是把活跃对象复制到新的内存,之前的内存空间可以被回收。
初始转移需要扫描 GC Roots 直接引用的对象并进行转移,这个过程需要 STW,STW 时间跟 GC Roots 成正比。
3.5 并发转移
并发转移过程 GC 线程和 Java 线程是并发进行的。上面已经讲过,转移过程中对象视图会被切回 Remapped 。转移过程需要注意以下几点:
- 如果 GC 线程访问对象的视图是 Marked0,则转移对象,并把对象视图设置成 Remapped。
- 如果 GC 线程访问对象的视图是 Remapped,说明被其他 GC 线程处理过,跳过不再处理。
- 并发转移过程中 Java 应用线程创建的新对象地址视图是 Remapped。
- 如果 Java 应用线程访问的对象被标记为活跃并且对象视图是 Marked0,则转移对象,并把对象视图设置成 Remapped。
3.6 重定位
转移过程对象的地址发生了变化,在这个阶段,把所有指向对象旧地址的指针调整到对象的新地址上。
四、垃圾收集算法
ZGC 采用标记 - 整理算法,算法的思想是把所有存活对象移动到堆的一侧,移动完成后回收掉边界以外的对象。如下图:
![](https://img.haomeiwen.com/i13587608/2ef8bc5b37d7e6e3.png)
4.1 JDK 16 之前
在 JDK 16 之前,ZGC 会预留(Reserve)一块儿堆内存,这个预留内存不能用于 Java 线程的内存分配。即使从 Java 线程的角度看堆内存已经满了也不能使用 Reserve,只有 GC 过程中搬移存活对象的时候才可以使用。如下图:
![](https://img.haomeiwen.com/i13587608/ad1239491d789f30.png)
这样做的好处是算法简单,非常适合并行收集。但这样做有几个问题:
- 因为有预留内存,能给 Java 线程分配的堆内存小于 JVM 声明的堆内存。
- Reserve 仅仅用于存放 GC 过程中搬移的对象,有点内存浪费。
- 因为 Reserve 不能给 GC 过程中搬移对象的 Java 线程使用,搬移线程可能会因为申请不到足够内存而不能完成对象搬移,这返回过来又会导致应用程序的 OOM。
4.2 JDK 16 改进
JDK 16 发布后,ZGC 支持就地搬移对象(G1 在 Full GC 的时候也是就地搬移)。这样做的好处是不用预留空闲内存了。如下图:
![](https://img.haomeiwen.com/i13587608/d1449be40d1df789.png)
不过就地搬移也有一定的挑战。比如:必须考虑搬移对象的顺序,否则可能会覆盖尚未移动的对象。这就需要 GC 线程之间更好的进行协作,不利于并发收集,同时也会导致搬移对象的 Java 线程需要考虑什么可以做什么不可以做。
为了获得更好的 GC 表现,JDK 16 在支持就地搬移的同时,也支持预留(Reserve)堆内存的方式,并且 ZGC 不需要真的预留空闲的堆内存。默认情况下,只要有空闲的 region,ZGC 就会使用预留堆内存的方式,如果没有空闲的 region,否则 ZGC 就会启用就地搬移。如果有了空闲的 region, ZGC 又会切换到预留堆内存的搬移方式。
五、ZGC什么时候触发垃圾回收
ZGC触发GC的时机主要有七种:
- 1、预热规则:一般不需要关注,日志中的关键字是Warmup。JVM启动预热,如果从来没有发生过GC,则在堆的使用率超过10%、20%、30%时,分别触发一次GC,以收集GC数据
- 2、基于分配速率得自适应算法:默认方式。根据近期对象分配速率以及GC时间,计算出当内存占有达到什么阈值时触发下一次GC。通过ZAllocationSpikeTolerance参数控制阈值大小,该参数默认2,数值越大,越早的触发GC。日志中关键字是“Allocation Rate”
- 3、基于固定时间间隔:通过ZCollectionInterval控制,适合应对突增流量场景。流量平稳变化时,自适应算法可能在堆的使用率达到95%以上才触发GC。流量突增时,自适应算法触发的时机可能会较晚,导致部分线程阻塞,此参数解决流量突增场景问题,如定时活动、秒杀等场景。日志中关键字是“Timer”
- 4、主动触发规则:类似于固定时间间隔,但时间间隔不固定,是ZGC自行计算出来的时机,如果我们的服务已经加了基于固定时间间隔的触发机制,可以通过-ZProactive参数将该功能关闭,以免GC频繁,影响服务可用性。
- 5、阻塞内存分配请求触发:当垃圾来不及回收,垃圾将堆占满时,会导致部分线程阻塞。我们应该避免出现这种触发形式,关键日志是"Allocation Stall"
- 6、外部触发:代码中显示调用System.gc()触发。日志关键字是System.gc()
- 7、元数据不足时导致,一般不需要关注。日志中关键字是"Metadata GC Threshold"。
六、ZGC的参数设置
ZGC 优势不仅在于其超低的 STW 停顿,也在于其参数的简单,绝大部分生产场景都可以自适应。当然,极端情况下,还是有可能需要对 ZGC 个别参数做个调整,大致可以分为三类:
• 堆大小:Xmx。当分配速率过高,超过回收速率,造成堆内存不够时,会触发 Allocation Stall,这类 Stall 会减缓当前的用户线程。因此,当我们在 GC 日志中看到 Allocation Stall,通常可以认为堆空间偏小或者 concurrent gc threads 数偏小。
• GC 触发时机:ZAllocationSpikeTolerance, ZCollectionInterval。ZAllocationSpikeTolerance 用来估算当前的堆内存分配速率,在当前剩余的堆内存下,ZAllocationSpikeTolerance 越大,估算的达到 OOM 的时间越快,ZGC 就会更早地进行触发 GC。ZCollectionInterval 用来指定 GC 发生的间隔,以秒为单位触发 GC。
• GC 线程:ParallelGCThreads, ConcGCThreads。ParallelGCThreads 是设置 STW 任务的 GC 线程数目,默认为 CPU 个数的 60%;ConcGCThreads 是并发阶段 GC 线程的数目,默认为 CPU 个数的 12.5%。增加 GC 线程数目,可以加快 GC 完成任务,减少各个阶段的时间,但也会增加 CPU 的抢占开销,可根据生产情况调整。
七、总结
内存多重映射和染色指针的引入,使 ZGC 的并发性能大幅度提升。
ZGC 只有 3 个需要 STW 的阶段,其中初始标记和初始转移只需要扫描所有 GC Roots,STW 时间 GC Roots 的数量成正比,不会耗费太多时间。再标记过程主要处理并发标记引用地址发生变化的对象,这些对象数量比较少,耗时非常短。可见整个 ZGC 的 STW 时间几乎只跟 GC Roots 数量有关系,不会随着堆大小和对象数量的变化而变化。
ZGC 也有一个缺点,就是浮动垃圾。因为 ZGC 没有分代概念,虽然 ZGC 的 STW 时间在 1ms 以内,但是 ZGC 的整个执行过程耗时还是挺长的。在这个过程中 Java 线程可能会创建大量的新对象,这些对象会成为浮动垃圾,只能等下次 GC 的时候进行回收。
参考:
https://www.likecs.com/show-204318492.html
https://blog.csdn.net/fedorafrog/article/details/113782570
https://blog.csdn.net/weixin_44335140/article/details/127219698
https://zhuanlan.zhihu.com/p/474527679
https://blog.csdn.net/fedorafrog/article/details/113782570
https://www.likecs.com/show-204318492.html
https://wiki.openjdk.java.net/display/zgc
https://openjdk.java.net/jeps/304
https://openjdk.java.net/jeps/376
https://malloc.se/blog/zgc-jdk16
https://mp.weixin.qq.com/s/ag5u2EPObx7bZr7hkcrOTg
https://mp.weixin.qq.com/s/FIr6r2dcrm1pqZj5Bubbmw
https://www.jianshu.com/p/664e4da05b2c
网友评论