一文详解Java高性能垃圾回收器ZGC,为什么比CMS、G1更快
ZGC(Z Garbage Collector)是在JDK 11 中引入一种新的垃圾回收器,旨在提供非常低的暂停时间和高的吞吐量,尤其适用于超大堆内存的应用。是一个可伸缩的低延迟垃圾回收器。
CMS与G1的停顿时间
CMS(Concurrent Mark-Sweep)垃圾回收器在初始标记和重新标记阶段需要STW(Stop-The-World),G1(Garbage-First)垃圾回收器在初始标记、最终标记、部分复制清理阶段、回收阶段等都需要STW。在STW期间会暂停用户线程,增加系统响应时间,降低系统可用性。
ZGC的主要特点
- 极低的暂停时间:
-
- ZGC的设计目标是将GC暂停时间控制在10毫秒以下,即使在超大堆内存(多达数TB)的情况下也能保持低暂停时间。
- 并发收集:
-
- ZGC的大多数工作都是并发进行的,最大限度地减少应用线程的暂停时间。包括标记、重新标记和压缩等阶段,都是在应用线程运行的同时进行的。
- 可伸缩性:
-
- ZGC能够有效地处理非常大的堆内存,适用于需要高可用性和低延迟的大型应用。
- 基于染色指针(Colored Pointers)和读屏障(Load Barriers) :
-
- ZGC引入了染色指针(Colored Pointers)和读屏障(Load Barriers)技术,以便精确追踪和管理内存中的对象。这些技术允许ZGC在GC过程中安全地移动对象而不需要长时间的暂停。
ZGC回收过程
与CMS中的ParNew和G1类似,ZGC也采用标记-复制算法,不过ZGC对该算法做了重大改进:ZGC在标记、转移和重定位阶段几乎都是并发的,这是ZGC实现停顿时间小于10ms目标的最关键原因。
ZGC垃圾回收周期如下图所示:
ZGC只有三个STW阶段:初始标记,再标记,初始转移。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。
ZGC关键技术
ZGC通过染色指针和读屏障技术,解决了转移过程中准确访问对象的问题,实现了并发转移。
大致原理描述如下:并发转移中“并发”意味着GC线程在转移对象的过程中,应用线程也在不停地访问对象。假设对象发生转移,但对象地址未及时更新,那么应用线程可能访问到旧地址,从而造成错误。而在ZGC中,应用线程访问对象将触发“读屏障”,如果发现对象被移动了,那么“读屏障”会把读出来的指针更新到对象的新地址上,这样应用线程始终访问的都是对象的新地址。那么,JVM是如何判断对象被移动过呢?就是利用对象引用的地址,即染色指针。下面介绍染色指针和读屏障技术细节。
染色指针(Colored Pointers)
ZGC使用染色指针来跟踪对象的状态,这种技术将状态信息存储对象指针中。染色指针包括以下几种状态:
- Marked0:对象在当前并发标记周期内被标记为存活。
- Marked1:对象在前一个并发标记周期内被标记为存活。
- Remapped:对象已经被重定位。
这种技术减少了内存开销,并提高了对象状态管理的效率。
ZGC仅支持64位系统,它把64位虚拟地址空间划分为多个子空间,如下图所示:
其中,[0~4TB) 对应Java堆,[4TB ~ 8TB) 称为M0地址空间,[8TB ~ 12TB) 称为M1地址空间,[12TB ~ 16TB) 预留未使用,[16TB ~ 20TB) 称为Remapped空间。
当应用程序创建对象时,首先在堆空间申请一个虚拟地址,但该虚拟地址并不会映射到真正的物理地址。ZGC同时会为该对象在M0、M1和Remapped地址空间分别申请一个虚拟地址,且这三个虚拟地址对应同一个物理地址,但这三个空间在同一时间有且只有一个空间有效。ZGC之所以设置三个虚拟地址空间,是因为它使用“空间换时间”思想,去降低GC停顿时间。“空间换时间”中的空间是虚拟空间,而不是真正的物理空间。后续章节将详细介绍这三个空间的切换过程。
与上述地址空间划分相对应,ZGC实际仅使用64位地址空间的第041位,而第4245位存储元数据,第47~63位固定为0。
ZGC将对象存活信息存储在42~45位中,这与传统的垃圾回收并将对象存活信息放在对象头中完全不同。
读屏障(Load Barriers)
ZGC使用读屏障(Load Barriers)技术来处理对象的访问和引用更新。每次应用线程加载一个对象引用时,都会触发读屏障逻辑。读屏障检查对象的状态,并根据需要执行额外的处理,如更新对象引用或处理对象的重定位。
ZGC触发时机
相比于CMS和G1的GC触发机制,ZGC的GC触发机制有很大不同。ZGC的核心特点是并发,GC过程中一直有新的对象产生。如何保证在GC完成之前,新产生的对象不会将堆占满,是ZGC参数调优的第一大目标。因为在ZGC中,当垃圾来不及回收将堆占满时,会导致正在运行的线程停顿,持续时间可能长达秒级之久。
ZGC有多种GC触发机制,总结如下:
- 阻塞内存分配请求触发: 当垃圾来不及回收,垃圾将堆占满时,会导致部分线程阻塞。我们应当避免出现这种触发方式。日志中关键字是“Allocation Stall”。
- 基于分配速率的自适应算法: 最主要的GC触发方式,其算法原理可简单描述为”ZGC根据近期的对象分配速率以及GC时间,计算出当内存占用达到什么阈值时触发下一次GC”。自适应算法的详细理论可参考彭成寒《新一代垃圾回收器ZGC设计与实现》一书中的内容。通过ZAllocationSpikeTolerance参数控制阈值大小,该参数默认2,数值越大,越早的触发GC。我们通过调整此参数解决了一些问题。日志中关键字是“Allocation Rate”。
- 基于固定时间间隔: 通过ZCollectionInterval控制,适合应对突增流量场景。流量平稳变化时,自适应算法可能在堆使用率达到95%以上才触发GC。流量突增时,自适应算法触发的时机可能会过晚,导致部分线程阻塞。我们通过调整此参数解决流量突增场景的问题,比如定时活动、秒杀等场景。日志中关键字是“Timer”。
- 主动触发规则: 类似于固定间隔规则,但时间间隔不固定,是ZGC自行算出来的时机,我们的服务因为已经加了基于固定时间间隔的触发机制,所以通过-ZProactive参数将该功能关闭,以免GC频繁,影响服务可用性。 日志中关键字是“Proactive”。
- 预热规则: 服务刚启动时出现,一般不需要关注。日志中关键字是“Warmup”。
- 外部触发: 代码中显式调用System.gc()触发。 日志中关键字是“System.gc()”。
- 元数据分配触发: 元数据区不足时导致,一般不需要关注。 日志中关键字是“Metadata GC Threshold”。
优势和局限性
优势
- 极低的暂停时间:
-
- ZGC的暂停时间通常在数毫秒以内,即使在超大堆内存情况下也能保持低暂停时间。
- 高可伸缩性:
-
- ZGC能够处理数TB级别的堆内存,适用于需要处理大量数据的大型应用。
- 并发处理:
-
- 大多数GC工作都并发进行,最大限度地减少了应用线程的停顿。
局限性
- 更高的CPU开销:
-
- 由于ZGC的大量工作是并发进行的,需要额外的CPU资源来处理读屏障和其他并发任务。
- 目前仅支持64位平台:
-
- ZGC目前只支持64位的Linux和macOS平台,不支持32位平台。
- 染色指针的局限性:
-
- 染色指针使用了对象引用的高位比特,这可能在某些情况下限制引用的使用。
配置与使用
ZGC(Z Garbage Collector)是一种旨在提供低延迟和高吞吐量的垃圾回收器。为了使ZGC在特定的应用场景中表现出最佳性能,我们可以通过一些常用的配置参数来调整其行为。以下是一些常用的ZGC配置参数及其解释:
启用ZGC
启用ZGC需要解锁实验性VM选项并指定使用ZGC:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
设置堆内存大小
设置初始堆内存(最小堆内存)和最大堆内存:
-Xms<size>
-Xmx<size>
例如,将初始堆内存设置为8GB,最大堆内存设置为32GB:
-Xms8g -Xmx32g
设置暂停时间目标
ZGC允许设置目标最大暂停时间(单位为毫秒):
-XX:MaxGCPauseMillis=<time>
例如,设置目标最大暂停时间为10毫秒:
-XX:MaxGCPauseMillis=10
并发GC线程数
设置并发垃圾回收线程的数量。默认情况下,ZGC会根据CPU核心数自动调整:
-XX:ConcGCThreads=<threads>
例如,设置并发GC线程数为4:
-XX:ConcGCThreads=4
并发标记周期间隔
设置并发标记周期之间的时间间隔(默认是动态调整的,不常用但可根据需要调整):
-XX:ZMarkCycleInterval=<time>
使用大页(Huge Pages)
启用或禁用大页支持,大页可以减少内存管理开销:
-XX:+UseLargePages
堆内存的占用上限
设置堆内存的使用上限,以避免使用系统的全部内存:
-XX:ZUncommitDelay=<time>
打印GC日志
启用GC日志打印,帮助诊断和调优:
-Xlog:gc:gc.log
可以进一步控制日志详细程度:
-Xlog:gc*=debug:file=gc.log
内存限制
设置ZGC的最小和最大区域大小:
-XX:ZCollectionPeriod=<time>
-XX:ZFragmentationLimit=<percentage>
-XX:ZMarkStackSpaceLimit=<size>
其他调优参数
- -XX:ZStatisticsInterval=1000:设置统计信息输出间隔(单位:毫秒)。
- -XX:ZHighUsageThreshold=70:设置高内存使用率的阈值(单位:百分比)。
- -XX:ZLowUsageThreshold=30:设置低内存使用率的阈值(单位:百分比)。
总结
ZGC是一种先进的低延迟垃圾回收器,适用于需要极低暂停时间和高可伸缩性的应用。尽管ZGC引入了复杂的技术,如染色指针和读屏障,但它提供了显著的性能优势,使其成为大规模Java应用的理想选择。
转载自:https://juejin.cn/post/7378028569689833513