一、什么是ZGC
ZGC 全称 The Z Garbage Collector 是JDK 11中推出的一款追求极致低延迟的垃圾收集器,注意至少是JDK11把版本才能支持,目前流行的主流版本不支持。它曾经设计目标包括:
1、停顿时间不超过10ms(JDK16已经达到不超过1ms);
2、停顿时间不会随着堆的大小,或者活跃对象的大小而增加;
3、支持8MB~4TB级别的堆,JDK15后已经可以支持16TB。
听起来很牛逼,接下来看看他是这么实现的:
二、ZGC垃圾回收过程
首先:ZGC中没有分代的概念(新生代、老年代)和G1一样,ZGC将内存划分成小的分区,在ZGC中称为页面(page)ZGC有三种页面
1、小页面、是2MB的页面空间、当对象大小小于等于256KB时,对象分配在小页面。
2、中页面、中页面指32MB的页面空间、当对象大小在256KB和4M之间,对象分配在中页面。
3、大页面、大页面指受操作系统控制的大页,当对象大于4M,对象分配在大页面。
整体流程:
1、初始阶段:这个阶段需要暂停(STW),初始标记只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,停顿时间不会随着 堆的大小或者活跃对象的大小而增加。
2、并发标记:这个阶段不需要暂停(没有STW),扫描剩余的所有对象,这个处理时间比较长,所以走并发,业务线程与GC线程同时运行。 但是这个阶段会产生漏标问题。
3、再标记:这个阶段需要暂停(没有STW),主要处理漏标对象,通过SATB算法解决(G1中的解决漏标的方案)。
4:并发转移准备 :分析最有回收价值GC分页(无STW)
5、初始转移(转移初始标记的存活对象同时做对象重定位(有STW)
6、并发转移(对转移并发标记的存活对象做转移<无STW>)
这个过程有几个点需要注意
1、ZGC对对象的标记不再是用对象头记录的方式,而是采用了颜色指针标记:颜色指针标记是指:ZGC只支持64位系统(使用64位指针),ZGC中低42位表示使用中的堆空间,然后、ZGC借几位高位来做GC相关的事情(快速实现垃圾回收中的并发标记、转移和重定位等)如图:
2、如何做到并发转移?
在并发转移的过程中需要记录转发表,由于ZGC采用颜射指针标记,当把存活对象复制到新的区域的时候,指向原本区域的指针如何能够知道该对象已经复制到了新的对象,从而指向新区域呢,这就需要在转移过程中记录一张转发表:记录从旧对象 到新对象的转向关系
3、读屏障
涉及对象:并发转移但还没做对象重定位的对象
触发时机:在两次GC之间业务线程访问这样的对象
触发操作:对象重定位+删除转发表记录(两个一起做原子操作)
读屏障是JVM向应用代码插入一小段代码的技术。当应用线程从堆中读取对象引用时,就会执行这段代码。
ZGC中GC触发机制:
1、预热规则:JVM启动预热,如果从来没有发生过GC,则在堆内存使用超过10%、20%、30%时,分别触发一次GC,以收集GC数据.
2、基于分配速率的自适应算法:最主要的GC触发方式(默认方式),其算法原理可简单描述为”ZGC根据近期的对象分配速率以 及GC时间,计算出当内存占用达到什么阈值时触发下一次GC”。通过ZAllocationSpikeTolerance参数控制阈值大小,该参数 默认2,数值越大,越早的触发GC。
3、基于固定时间间隔:通过ZCollectionInterval控制,适合应对突增流量场景。流量平稳变化时,自适应算法可能在堆使用率达 到95%以上才触发GC。流量突增时,自适应算法触发的时机可能会过晚,导致部分线程阻塞。我们通过调整此参数解决流量突 增场景的问题,比如定时活动、秒杀等场景。
4、主动触发规则:类似于固定间隔规则,但时间间隔不固定,是ZGC自行算出来的时机,我们的服务因为已经加了基于固定时间 间隔的触发机制,所以通过-ZProactive参数将该功能关闭,以免GC频繁,影响服务可用性。
5、 阻塞内存分配请求触发:当垃圾来不及回收,垃圾将堆占满时,会导致部分线程阻塞。我们应当避免出现这种触发方式。日志 中关键字是“Allocation Stall”。
6、外部触发:代码中显式调用System.gc()触发。 日志中关键字是“System.gc()”。
7、元数据分配触发:元数据区不足时导致,一般不需要关注。 日志中关键字是“Metadata GC Threshold”。
ZGC参数设置:
1、堆大小:Xmx。当分配速率过高,超过回收速率,造成堆内存不够时,会触发 Allocation Stall,这 类 Stall 会减缓当前的用户线程。因此,当我们在 GC 日志中看到 Allocation Stall,通常可以认为堆空间 偏小或者 concurrent gc threads 数偏小。
2、GC 触发时机:ZAllocationSpikeTolerance, ZCollectionInterval。ZAllocationSpikeTolerance 用 来估算当前的堆内存分配速率,在当前剩余的堆内存下,ZAllocationSpikeTolerance 越大,估算的达到 OOM 的时间越快,ZGC 就会更早地进行触发 GC。ZCollectionInterval 用来指定 GC 发生的间隔,以秒 为单位触发 GC。
3、GC 线程:ParallelGCThreads, ConcGCThreads。ParallelGCThreads 是设置 STW 任务的 GC 线 程数目,默认为 CPU 个数的 60%;ConcGCThreads 是并发阶段 GC 线程的数目,默认为 CPU 个数的 12.5%。增加 GC 线程数目,可以加快 GC 完成任务,减少各个阶段的时间,但也会增加 CPU 的抢占开 销,可根据生产情况调整
ZGC 需要调整的参数十分简单,通常设置 Xmx 即可满足业务的需求,大大减轻 Java 开发者的负担
三、ZGC的优势
1、一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理,这使得理论上只要还有一个空闲Region,ZGC就能完成收集。
2、颜色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,ZGC只使用了读屏障。
3、颜色指针具备强大的扩展性,它可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能。
四、缺点:
它能承受的对象分配速率不会太高,ZGC准备要对一个很大的堆做一次完整的并发收集。在这段时间里面,由于应用的对象分配速率很高,将创造大量的新对象,这些新对象很难进入当次收集的标记范围,通常就只能全部当作存活对象来看待——尽管其中绝大部分对象都是朝生夕灭的,这就产生了大量的浮动垃圾。如果这种高速分配持续维持的话,每一次完整的并发收集周期都会很长,回收到的内存空间持续小于期间并发产生的浮动垃圾所占的空间,堆中剩余可腾挪的空间就越来越小了。目前唯一的办法就是尽可能地增加堆容量大小,获得更多喘息的时间。
五、如何选择垃圾收集器:
1、优先调整堆的大小让服务器自己来选择
2、如果内存小于100M,使用串行收集器
3、如果是单核,并且没有停顿时间的要求,串行或JVM自己选择
4、如果允许停顿时间超过1秒,选择并行或者JVM自己选
5、如果响应时间最重要,并且不能超过1秒,使用并发收集器
6、4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,几百G以上用ZGC