新一代 Java垃圾回收神器:ZGC

今天我们分享的内容是:新一代 Java垃圾回收神器:ZGC。

ZGC 定义

ZGC(The Z Garbage Collector),是一种可扩展的低延迟垃圾收集器,主要是用来处理超大内存(TB级别)的垃圾回收。
ZGC 最初是 JDK 11 以一项实验性功能引入的,经过几个版本的迭代,最终在 JDK 15中被宣布为 Production Ready。

ZGC的中的"Z"代表什么含义?官方解释如下:

大概意思为:Z它不代表任何东西,ZGC只是一个名字。它最初受到 ZFS(文件系统)的启发或致敬,ZFS(文件系统)在首次问世时在许多方面都是革命性的。最初,ZFS 是“Zettabyte File System”的首字母缩写词,但这个意思被废弃了,后来据说它不代表任何东西。这只是一个名字。有关详细信息,请参阅Jeff Bonwick 的博客。

下图为 Oracle官方对 ZGC停顿时间的描述:

ZGC的目标

2018年,Oracle官方描述的 ZGC目标为:

  1. 处理 TB 量级的堆;
  2. GC 时间不超过 10ms;
  3. 相对于使用 G1,应用吞吐量的降低不超过 15%;

2022年11月,Oracle官方描述的 ZGC的目标为:

  1. 低延时,亚毫秒的最大暂停时间
  2. 暂停时间不会随着堆、live-set 或 root-set 的大小而增加
  3. 处理 TB 量级的堆(可以处理 8MB-16TB 的堆);

通过官方对 ZGC目标的描述也可以看出,在几年的时间内,ZGC垃圾回收器的最大停顿时间已从 10ms 降低到 亚毫秒 级别,性能有了质的飞越。

核心技术点

2018年,官方描述的 ZGC的核心技术点为:

2022年,官方描述的 ZGC的核心技术点为:

比较 2018年 和 2022年官方对 ZGC核心技术的描述可以发现,官方把 Single generation 这一点去掉了,熟悉 G1的小伙伴应该知道,G1是 Region-based类型,每个 Region在同一时刻只能属于一种分代,但是,一个Region可以在多个分代之间动态切换,因此,ZGC 从 最初的不分代发展成和 G1一样,也有分代。

ZGC 原理

Region-based

Region-based:基于区域。

ZGC 和 G1等垃圾回收器一样,也会将堆划分成很多的小分区,整个堆内存分区如下图:

ZGC的 Region 有小、中、大三种类型:

Compacting

Compacting:整理内存。

因为 ZGC回收器和 CMS、G1等垃圾回收器一样,使用了"标记-复制算法",该算法会产生内存碎片,因此需要进行内存整理操作,清除内存碎片。

NUMA

NUMA,Non-Uniform Memory Access(非一致内存访问)。

最初的计算机是单核处理器,一个 CPU访问一块内存,但是随着网络的快速发展,单核远不能满足实际需求,因此采用多核处理器技术,多 CPU需要访问同一个内存,因为任一 CPU对同一内存的访问速度是一致的,所以也称作一致内存访问(Uniform Memory Access, UMA),再随着网络的发展,单内存无法满足需求,因此就诞生了多内存,把 CPU和内存集成到同一个单元,这样 CPU就会访问离它最近的内存,提升读写效率,这种方式就是非一致内存访问。

NUMA和UMA比较如下图:

NUMA 架构在中大型系统上非常流行,是一种高性能的解决方案,ZGC就充分利用 NUMA架构的特征。

Colored pointers

Colored pointers:染色指针,一种将数据存放在指针里的技术,JVM是通过染色指针来标识某个对象是否需要被GC。
像 CMS,G1这些垃圾收集器的 GC信息都保存在对象头中,而 ZGC的 GC信息保存在指针中,每个对象有一个 64位指针,参考 JDK zGlobals_x86.cpp 源码,ZGC 地址空间和指针结构有如下 3种布局:

布局1

抽象成结构图如下:

布局2

抽象成结构图如下:

布局3

抽象成结构图如下:

通过上面的 3种布局,可以发现一个共性:分配 4-bits来分别存放 Marked0,Marked1,Remapped,Finalizable 染色标记位,4种染色标记位说明如下:

多重视图

上述 Marked0,Marked1,Remapped染色标记位其实代表一种地址视图,当应用程序创建对象时,首先在堆空间申请一个虚拟地址,ZGC同时会为该对象在 Marked0、Marked1和 Remapped地址空间分别申请一个虚拟地址,三个虚拟地址指向同一个物理地址,并且在同一时间,三个虚拟地址有且只有一个空间有效,整个视图映射关系如下图:

ZGC之所以设置三个虚拟地址空间,目的是使用"虚拟空间换时间"的思想,从而降低GC停顿时间。三个空间的切换是由垃圾回收的不同阶段触发的,通过限定三个空间在同一时间点有且仅有一个空间有效高效的完成GC过程的并发操作。

Load barriers

Load barriers:读屏障,是指 JIT( just-in-time compilation 即时编译器,JVM)向应用代码注入一小段代码,当从堆中读取对象引用时,就会执行这段代码,官方说明如下:

读屏障示例:

java复制代码String n = person.name; // 从堆中读取引用,需要加入屏障  
  
  
if (n & bad_bit_mask) {  
slow_path(register_for(n), address_of(person.name));  
}  
  
  
String p = n ; // 无需屏障,不是从堆中读取引用  
n.isEmpty() ; // 无需屏障,不是从堆中读取引用  
int age = person.age; // 无需屏障,不是对象引用  

在读屏障示例中,JVM 注入了如下的一段读屏障代码:

text复制代码if (n & bad_bit_mask) {  
slow_path(register_for(n), address_of(person.name));  
}  

对应的字节码如下:

java复制代码mov 0x10(%rax), %rbx // String n = person.name;  
test %rbx, 0x20(%r15) // Bad color?  
jnz slow_path // Yes -> Enter slow path and  
// mark/relocate/remap, adjust  
// 0x10(%rax) and %rbx  

假如 person 对象发生移动,因此 n 和 person.name 的地址都会发生变化,当使用 n 前,需要判断 n 的染色指针是否为 good,如果为 bad color,可以得知 n 的引用地址被修改过,因此需要修正 n 和 person.name的地址,整个过程如下图:

上图过程也称为"自愈",即当对象地址发生转移时,通过读屏障操作,不仅赋值的引用更改为最新值,自身引用也被修正了,整个过程看起来像自愈。

这里对"转移"做个解释:ZGC是按照 Page内存页进行垃圾回收的,也就是说当对象所在的页面需要回收时,页面里还存活的对象需要被转移到其他页。

Concurrent

Concurrent:并发,指 GC线程和应用线程是并发执行。

和 MCS、G1等垃圾回收器一样,ZGC也采用了标记-复制算法,不过,ZGC对标记-复制算法做了很大的改进,ZGC垃圾回收周期和视图切换可以抽象成下图:

下图以 obj1,obj2,obj3 三个对象为案例对垃圾回收和视图切换进行了演示:

ZGC 全过程

ZGC垃圾回收全过程包含:初始标记、并发标记、再标记、并发转移准备、初始转移、并发转移 6个阶段,如下图:

在 ZGC 全过程中,会出现 3个 STW(Stop The World):初始标记,再标记,初始转移。尽管这 3个阶段会 STW,但是 ZGC对 STW的暂停时间是有严格要求,一般是 1ms 甚至更低,下面将分别介绍各个阶段:

常见问题

为什么需要 Marked0 和 Marked1 两个标识?

简单地说是为了区别上一次标记和当前标记,因为每个 GC周期开始时,会交换使用的 Marked标记位,使上次 GC周期中修正的已标记状态失效,所有引用都变成未标记。
比如:GC周期1 使用Marked0,则 GC周期结束后,所有引用 Marked0标记都会成为 01(二进制的01 = 0);GC周期2使用 Marked1,类同于周期1,所有的 Marked标记都会成为 10(二进制的10 = 1)。

为什么会有 3种内存布局?

这主要还是和 ZGC的目标(支持 8MB-16TB 的堆)相匹配,因为 ZGC需要支持最大 16TB Java堆内存的垃圾回收,所以就需要用不同 bit位数来表示,因此就出现了3种布局。

使用多重视图和染色指针的优点

像 CMS,G1等垃圾回收器,GC信息是存放在对象头中,因此每次修改对象头信息时都需要先访问内存,然后操作,而 ZGC是把 GC信息存放在指针的有色标记位上,修改GC信息时,无需任何对象访问,只需要设置地址中对应的标志位即可,因此可以加快标记和转移的速度,这也是 ZGC在标记和转移阶段速度更快的原因。

Supported Platforms

ZGC 支持哪些平台?

下面是官方文档列举的所有支持平台:目前,ZGC目前支持了大多数的操作系统,并且是 64位系统。


重要配置参数

启用 ZGC

shell复制代码-XX:+UseZGC -Xmx -Xlog:gc  
# 或者  
-XX:+UseZGC -Xmx -Xlog:gc*  

ZGC的 JVM选项

ZGC的发展

ZGC 最初是作为 JDK 11 中的一项实验性功能引入的,并在 JDK 15 中被宣布为 Production Ready

从 不支持类卸载 到 支持类卸载

从仅支持个别系统到支持多个平台

从不支持指针压缩,到支持压缩类指针

JDK 16 支持并发线程堆栈扫描

......

ZGC 随着JDK发展的更改日志如下:

通过 ZGC的发展可以看出:GC垃圾回收器已经越来越智能化,GC会自适应各种情况自动优化。

总结

展开阅读全文

页面更新:2024-05-22

标签:垃圾   新一代   神器   屏障   视图   线程   指针   标记   对象   内存   阶段   地址

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top