关于垃圾收集器ZGC的扩展

  • Post author:
  • Post category:其他




一、ZGC背景

Java项目在JVM进行垃圾回收时有个很大的问题,即

STW

(Stop The World)。

当JVM进行垃圾回收时,会暂停所有的应用程序线程,即业务线程,因而导致业务系统的暂停。

在这里插入图片描述



STW引发的问题



1、手机系统(Android) 显示卡顿

Google 主导的 Android 系统需要解决的一大问题就是显示卡顿问题,通过对 GC 算法的不断演进,停顿时间控制在几个ms级别。

所以这也是Android与苹果IOS系统竞争的一大利器。



2、证券交易系统实时性要求

证券交易系统主要就是买入与卖出,现在都是使用系统完成自动下单。如果用Java系统来做,遇到了STW,假设STW的时间是3秒,此时刚收到市场行情是比较低而进行买入,但是因为STW卡顿了3秒,3秒后的市场行情可能完全不同。所以若使用Java来做证券系统,一定是要求STW时间越短越好!



3、大数据平台(Hadoop集群性能)

例如58同城、腾讯、阿里等公司的大数据系统,单集群5000+的Hadoop集群,日万亿级实时数据分发,若遇到STW也是不行的。



垃圾回收器的发展

为了满足不同的业务需求,Java 的 GC 算法也在不停迭代,对于特定的应用,选择其最适合的 GC 算法,才能更高效的帮助业务实现其业务目标。对于这些延迟敏感的应用来说,GC 停顿已经成为阻碍 Java 广泛应用的一大顽疾,需要更适合的 GC 算法以满足这些业务的需求。

近些年来,服务器的性能越来越强劲,各种应用可使用的堆内存也越来越大,常见的堆大小从10G到百G级别,部分机型甚至可以到达TB级别,在这类大堆应用上,传统的GC,如CMS、G1的停顿时间也跟随着堆大小的增长而同步增加,即堆大小指数级增长时,停顿时间也会指数级增长。特别是当触发Full GC时,停顿可达分钟级别(百GB级别的堆)。当业务应用需要提供高服务级别协议(Service Level Agreement,SLA),例如 99.99% 的响应时间不能超过 100ms,此时CMS、G1等就无法满足业务的需求。

为满足当前应用对于超低停顿、并应对大堆和超大堆带来的挑战,伴随着 2018 年发布的 JDK 11,A Scalable Low-Latency Garbage Collector – ZGC 应运而生。

在这里插入图片描述

单线程:Serial、SerialOld

多线程:ParallelScavenge、ParallelOld

多线程+并发:CMS、G1、Shenandoah、ZGC(STW控制在1ms)



二、ZGC介绍

ZGC(The Z Garbage Collector)是JDK 11中推出的一款追求极致低延迟的垃圾收集器。

设计目标:

  • 停顿时间不超过10ms(JDK16已经达到不超过1ms)
  • 停顿时间不会随着堆的大小,或者活跃对象的大小而增加
  • 支持8MB~4TB级别的堆(JDK15后已经可以支持16TB)

若使用ZGC来做Java项目,像对STW敏感的证券系统,游戏的系统便都可以去用Java来做(以前都是C或者C++的市场),所以ZGC的出现就是为了抢占其他语言的市场!



三、ZGC中的内存布局(分页模型)

为了细粒度地控制内存的分配,和G1一样,ZGC将内存划分成小的分区,在ZGC中称为页面(page)。


ZGC中没有分代的概念(新生代、老年代)。

ZGC支持3种页面,分别为

小页面、中页面和大页面

其中小页面指的是2MB的页面空间,中页面指32MB的页面空间,大页面指受操作系统控制的大页面空间。

在这里插入图片描述

当对象大小小于等于256KB时,对象分配在小页面。

当对象大小在256KB和4M之间,对象分配在中页面。

当对象大于4M,对象分配在大页面。

ZGC对于不同页面回收的策略也不同。简单来说:

小页面优先回收,中页面和大页面则尽量不回收



设计理由

标准大页(huge page)是Linux Kernel 2.6引入的,目的是通过使用大页内存来取代传统的4KB内存页面,以适应越来越大的系统内存,让操作系统可以支持现代硬件架构的大页面容量功能。

Huge pages有两种格式大小:2MB(默认)和1GB。2MB页块大小适合用于GB大小的内存,1GB页块大小适合用于TB级别的内存。

因此ZGC的设置也是为了适应现代硬件架构的发展,提升性能。



ZGC支持NUMA

在过去,对于X86架构的计算机,内存控制器还没有整合进CPU,所有对内存的访问都需要通过北桥芯片来完成。X86系统中的所有内存都可以通过CPU进行同等访问。任何CPU访问任何内存的速度是一致的,不必考虑不同内存地址之间的差异,即统一内存访问(Uniform Memory Access,UMA)。


UMA系统的架构示意:


在这里插入图片描述

在UMA中,各处理器与内存单元通过互联总线进行连接,各个CPU之间没有主从关系。之后的X86平台经历了一场从“拼频率”到“拼核心数”的转变,越来越多的核心被尽可能地塞进了同一块芯片上,各个核心对于内存带宽的争抢访问成为瓶颈。

为了应对此问题便设计出非统一内存访问(Non-Uniform Memory Access,NUMA),将CPU和内存集成在一个单元上(称为Socket)。

在NUMA下,CPU访问本地存储器的速度显然比访问非本地存储器快一些。


NUMA处理器架构示意:


在这里插入图片描述

ZGC支持NUMA,在进行小页面分配时会优先从本地内存分配,当不能分配时才会从远端的内存分配。对于中页面和大页面的分配,ZGC并没有要求从本地内存分配,而是直接交给操作系统,由操作系统找到一块能满足ZGC页面的空间。

ZGC如此设计的目的在于,对于小页面,存放均为小对象,从本地内存分配速度很快,且不会造成内存使用的不均衡;而中页面和大页面由于需要的空间大,若也优先从本地内存分配,极易造成内存使用不均衡,反而影响性能。



四、ZGC的核心概念



指针着色技术(Colored Pointers)

颜色指针为ZGC的核心概念,它会在指针中借4位用来记录,因此它必须要求在64位的机器上才可以工作,也由于是64位的指针,因此也不支持压缩指针。

指针中低42位表示使用中的堆空间,高18位为预留空间,而中间的4位则被ZGC所使用。

ZGC借用此4位来做GC相关的事情(例如快速实现垃圾回收中的并发标记、转移和重定位等)。

在这里插入图片描述



五、ZGC流程



一次ZGC流程:

在这里插入图片描述



1、标记阶段:标识垃圾(根据根可达分析算法)



根可达分析算法

用来判定对象是否存活。

将一系列作为“GC Roots”的对象为起始点,从这些节点开始向下扫描,所形成的路径称为引用链(Reference Chain),在引用链上的对象则会被标记为非垃圾对象。

即当一个对象没有在GC Roots的引用链中,则说明此对象是无用的。

还有另一种算法为程序计数法,根据对象被其他对象引用的数量来判定,若引用计数为0则说明没有被任何对象引用,判定为垃圾对象。

但无法解决对象间

循环引用

的问题,即两个垃圾对象互相引用,但实际上程序已不再使用,而根可达分析算法可以解决这个问题,只要对象不被GC Roots所引用即为垃圾对象。

根可达分析算法的标记效率也比程序计数法高。



GC Roots的对象(以下4种为主要)


  1. 虚拟机栈(栈帧中的本地变量表):

    各个线程调用方法堆栈中使用到的参数、局部变量、临时变量等。


  2. 方法区中类静态变量:

    Java类的引用类型静态变量。


  3. 方法区中常量:

    例如字符串常量池里的引用。


  4. 本地方法栈中JNI指针:

    native修饰的方法。

GC Roots的s表示Set,即为GC根的集合



ZGC中垃圾收集算法流程

根据根可达分析算法提高收集效率。


  1. 标记(mark):

    从GC Roots出发,标记存活对象,此时内存中存在存活对象和死亡对象。


  2. 转移(relocate):

    将存活对象转移(复制)到新的内存上,原来的内存空间可以回收。


  3. 重定位(remap):

    由于对象的内存地址发生改变,因此要将所有指向旧地址的指针变更为指向新地址。



2、转移阶段:对象复制或移动


若是同一个页面,等同于标记整理算法:


在这里插入图片描述


若是不同页面,等同于复制算法:


在这里插入图片描述



3、ZGC基于指针着色的并发标记算法



0.初始阶段

在ZGC初始化后,此时地址视图为Remapped,程序正常运行,在内存中分配对象,满足一定条件后垃圾收集启动。



1.初始标记

此阶段会STW,初始标记只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,停顿时间不会随着堆的大小或者活跃对象的大小而增加。

相邻的两次GC会有时间间隔,M0和M1切换使用

第一次GC时,M0标记所有GC Roots的指针

第二次GC时,M1标记所有GC Roots的指针



2.并发标记

此阶段不会STW,扫描剩余的所有对象,处理时间比较长,所以使用并发,业务线程与GC线程会同时运行,也因此会出现漏标。

第一次GC时,M0标记GC Roots下的引用链指针

第二次GC时,M1标记GC Roots下的引用链指针



3.再标记

此阶段会STW,主要处理漏标对象,通过SATB算法解决(G1中的解决漏标的方案)。

第一次GC时,M0标记漏标对象指针

第二次GC时,M1标记漏标对象指针



4、ZGC基于指针着色的并发转移算法



ZGC的转移阶段


  1. 并发转移准备:

    分析哪个GC分页垃圾多,若有分页全是垃圾,会直接清除此分页的垃圾,不会STW


  2. 初始转移:

    转移初始标记的存活对象(即被M0标记的指针引用对象复制到新内存),同时做对象重定位(即将指针指向从旧内存地址改成新内存地址),会STW

此时被转移的对象会被Remapped标记,刚new出来的对象也会被Remapped标记


  1. 并发转移:

    对转移并发标记的存活对象做转移,不会STW



并发转移

根据转发表(类似于HashMap)来确定最新引用地址,表里记录指针及其新旧内存地址的映射。

对象转移和插转发表做原子操作。



5、ZGC基于指针着色的重定位算法


并发标记对象的重定位:


第二次GC中的并发标记阶段,除了标记GC Roots的引用链指针外,同时还会做上次并发标记对象的重定位,做好重定位之后便将转发表利对应的记录删除。

在这里插入图片描述



版权声明:本文为weixin_45925485原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。