ZGC 有多强 ,一次简单实操让你看的明明白白的

4,140 阅读11分钟

👈👈👈 欢迎点赞收藏关注哟

首先分享之前的所有文章 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…

一. 前言

ZGC 是 Java 高版本新一代垃圾回收器 ,到现在 JDK21 版本已经可以稳定使用。

前几篇我对 JDK G1 回收器进行了一些检测, 这一期就来体验一下, 作为新一代垃圾回收器有多强呢?

二. 其他回收器对比图

2.1 G1 回收器性能

// 基于 JDK 11 
-XX:+UseG1GC
-XX:+UnlockExperimentalVMOptions
-Xms512m
-Xmx2048m

image.png

2.2 默认回收器性能 (Parallel)

// 基于 JDK 8
-XX:+UnlockExperimentalVMOptions
-Xms512m
-Xmx2048m

默认回收器 (Parallel Scavenge + Parallel Old)

这里我使用的是 OpenJDK 8u412 版本 ,此版本里面新生代和老年代回收都走的 Parallel ,我们可以明显看到以下特性 :

image.png

  • 整体回收的节奏完全不一样 (前半部分是 G1 的数据效果 ,后面是 Parallel 的执行效果)

image.png

三. ZGC 切换体验

  • 关于 ZGC 的更底层的原理就不在这一篇来学习了 ,我们主要从体验上来了解一下它。

3.1 来回顾一下 ZGC

ZGC 提出的最大的3个目标 :

  • 低停顿时间 :不管多大的堆 ,将垃圾回收的停顿时间控制在 10 ms 以内
  • 大堆支持 : 可以支持特别大的堆 ,TB 级别的数据量
  • 并发处理 : 垃圾回收过程中除了个别操作 ,极大部分都是并发进行的

3.2 ZGC 常规性能

image.png

  • 单纯从跑的结果来看 ,JVM 堆的回收变化是很大的
  • 触发的各种垃圾回收的行为变得更加频繁
  • 回收的时间杂乱无章,参数变得更多了 ,他们分别表示什么呢?

❓为什么这里的 Pause Durations 指标超过了 10ms ?

我知道肯定很多小伙伴会问这个问题 ,为什么 ZGC 都说了 ,停顿时间 10ms 以内 ,这里看起来明显不止。

这里我们要继续回顾一个概念 ,不管是 ZGC 还是 G1 , 其控制的这个停顿时间 ,都是期望停顿时间。

同时这个图表里面分为 GC 结束时间和 GC 暂停时间。 整个 GC 的处理时间是很长的 ,但是 STW 的时间没想象的那么多长 : (从下面图片能看到 ,暂停的时间其实很短)

image.png

  • 所以这里为了能更清晰的了解 ZGC 垃圾回收的情况 ,我们再对日志分析一波 :
// 先简单科普一下 XLog : 
从 JDK 9 开始 , JVM 的日志就有了比较大的变化 ,后续开始可以通过 XLog 对日志进行分析 : 

> XLog 命令标准格式 :
-Xlog Usage: Xlog[:[selections][:[output][:[decorators][:output-options]]]]


> selections : 日志标签(tag)和日志等级(level)的组合
    - tag : 用于控制哪一个范围的信息 ,例如
    - level : 用于控制日志的等级 off、trace、debug、info、warning、error


// 常见案例 : 
> -Xlog:os=warning,error
= 记录操作系统相关的警告和错误日志

> -Xlog:all=debug:file=debug.log
= 记录所有的调试日志到文件

> -Xlog:gc+thread=info:stdout:time,tid
= 记录 GC 和线程日志到控制台,并带有时间和线程 ID

image.png

后文我们会讲到 ,这里先知道 ,ZGC 有3个 STW 的阶段 : 初始标记再标记初始转移

  • 初始标记 : Pause Mark Start 0.021ms
  • 再标记 :Pause Mark End 0.023ms
  • 初始转移 : Pause Relocate Start 0.006ms

而时间段上面的最大耗时也不大 :

image.png

好了 ,收 ,这一块这一期就看这么多,这些参数有很多我还没深入,就不细说了

更加直观的感受 :

image.png

  • 👉 回收频率侧参数 (左侧) :
参数描述
end of GC cycle (High Usage)在高内存使用的情况下,垃圾回收周期结束时的统计数据。适用于内存使用达到一定比例或阈值时的 GC 事件。
end of GC cycle (Metadata GC Threshold)在达到元数据垃圾回收阈值时,垃圾回收周期结束时的统计数据。适用于 JIT 编译、类加载等操作的元数据收集情况。
end of GC cycle (Proactive)主动触发垃圾回收周期结束时的统计数据。适用于系统在内存压力尚未明显时,为了优化性能主动触发的 GC 事件。
end of GC cycle (Warmup)在系统启动或热身阶段结束时的垃圾回收周期的统计数据。通常在应用程序的启动或初期阶段进行的 GC 操作。
end of GC pause (High Usage)在高内存使用的情况下,STW 暂停结束时的统计数据。适用于在内存使用达到一定比例或阈值时导致的应用程序暂停。
end of GC pause (Metadata GC Threshold)在达到元数据垃圾回收阈值时,STW 暂停结束时的统计数据。与元数据相关的操作如类加载导致的应用程序暂停。
end of GC pause (Proactive)主动触发垃圾回收暂停结束时的统计数据。用于优化系统性能,主动进行的应用程序暂停操作。
end of GC pause (Warmup)在系统启动或热身阶段结束时的 STW 暂停统计数据。通常在应用程序的启动或初期阶段的短暂停操作。
  • 👉 回收时间侧参数 (右侧 ):
参数类别参数名称简要说明
平均 GC 周期结束时间 (avg end of GC cycle)High Usage在高资源使用情况下的平均 GC 周期结束时间
Metadata GC Threshold当达到元数据阈值时的平均 GC 周期结束时间
Proactive在主动触发 GC 情况下的平均 GC 周期结束时间
Warmup系统启动或“暖机”期间的平均 GC 周期结束时间
平均 GC 暂停结束时间 (avg end of GC pause)High Usage在高资源使用情况下的平均 GC 暂停时间
Metadata GC Threshold当达到元数据阈值时的平均 GC 暂停时间
Proactive在主动触发 GC 情况下的平均 GC 暂停时间
Warmup系统启动或“暖机”期间的平均 GC 暂停时间
最大 GC 周期结束时间 (max end of GC cycle)High Usage在高资源使用情况下的最大 GC 周期结束时间
Metadata GC Threshold当达到元数据阈值时的最大 GC 周期结束时间
Proactive在主动触发 GC 情况下的最大 GC 周期结束时间
Warmup系统启动或“暖机”期间的最大 GC 周期结束时间
最大 GC 暂停结束时间 (max end of GC pause)High Usage在高资源使用情况下的最大 GC 暂停时间
Metadata GC Threshold当达到元数据阈值时的最大 GC 暂停时间
Proactive在主动触发 GC 情况下的最大 GC 暂停时间
Warmup系统启动或“暖机”期间的最大 GC 暂停时间

四. 观测结果总结

4.1 观测特性一 : 没有明显的 FullGC 和 内存变化了

ZGC 的每一次回收都是对整个堆的处理 ,所以每一次垃圾回收都是一次 FullGC.

4.2 观测特性二 : 内存如何划分?

和 G1 回收器的 Region 很类似 ,ZGC 采用了基于页面(Page)的分页管理 ,来对内存空间进行划分。

同时 ZGC 实现了两级内存管理 :虚拟内存 和 物理内存。 通过映射关系将两者进行了关联。

  • 物理内存 : 在最开始发布的时候 ,ZGC 支持 4TB 的内存空间。 JDK15 之后 ,支持了16TB的内存空间。
  • 虚拟内存 : ZGC 通过将虚拟内存映射到物理内存上面 ,同时通过多试图映射 ,让多个虚拟内存映射到一个物理内存上面。 通过这种映射关系 ,实现了后续的一系列内存控制。

除了划分上面的变动 , ZGC 另外一大要点就是页的处理:

  • ZGC 里面有3种页面 : 小页面 (2M)/ 中页面(32M)/ 大页面(操作系统控制的大页)
  • ZGC 的页是一种对内存的抽象 ,而操作系统的页是管理物理内存的单位 ,不可以当成同一个概念
  • 针对不同类型的页会有不同的控制策略 : 小页面优先回收 ,中页面和大页面则尽量不回收

深度就在这了 ,后面更详细的组合我也没深入,这一期只需要了解一个概念

4.3 观测特性三 : 没有了分代的操作 ,垃圾回收会在什么环节被触发?

ZGC 的垃圾回收触发是基于 ZDirector 实现的 ,它具有以下的策略 :

  • 基于固定时间间隔触发 : 让 ZGC的垃圾回收器以固定的频率触发
  • 预热规则触发 : 当JVM刚启动时,还没有足够的数据来主动触发垃圾回收的启动,所以设置了预热规则
  • 根据分配速率 : 根据分配速率来预测是否能触发垃圾回收。
  • 主动触发 : 在吞吐量下降的情况下,当满足一定条件时,还可以执行垃圾回收
  • 阻塞内存分配请求触发 : 如果不能成功分配内存,则触发阻塞内存分配
  • 外部触发 : 在Java代码中显式地调用System.gc()函数
  • 元数据分配触发 :元数据分配失败时,ZGC会尝试进行垃圾回收以确保元数据能正确分配

4.4 观测特性物 : 整个垃圾回收的流程变化

来简单了解一下 ZGC 整个算法的流程 :

  1. 初始标记 : 从根集合出发,找出根集合直接引用的活跃对象,并入栈
  2. 并发标记 : 根据初始标记的结果 ,作为并发标记的根对象,遍历对象的成员变量进行标记
  3. 再标记和非强根并行标记 :由于并发标记是并行的,理论上存在引用表达影响结果 ,这一步则会判断是否可以结束标记 。 如果存在引用变化 ,则进行并行标记。
  4. 并发处理非强引用和非强根并发标记 : 对定义了finalize()函数的对象需要特殊处理 。 同时对一些要在 STW 中执行的操作预先并行执行
  5. 重置转移集合中的页面
  6. 回收无效的页面
  7. 并发选择对象的转移集合
  8. 并发初始化转移集合中的每个页面
  9. 初始转移根对象引用的对象
  10. 并发转移

这里还有一个小细节 ,可能大家会注意到 ,转移后并没有再触发回收! .

这是因为转移完成后可回收的页面是在下一次垃圾回收周期回收的。 也就是每次算法的第六步。

除了这些 ,我们先不考虑每个过程做了什么。 其实整个流程就是这样 :

  • 对象被创建 👉 可以被回收了 👉 被标记 👉 先把不需要回收的进行转移 👉 留下要回收的在无效页 👉 下一次回收

image.png

4.5 停顿时间是否达到了预期 ?

ZGC只有三个STW阶段:初始标记再标记初始转移

那么除了这3个阶段 ,来看看其他阶段的性能效果 :

  • 并发标记 / 并发定位 :通过并发进行的方式 ,避免了 STW 的产生
  • 在并发环节中对一些 STW 的操作进行预处理 , 避免了后续耗时
  • 支持多线程处理 ,提高回收的效率

具体还有一些其他的策略 ,从这些简单的描述里面 ,我其实没弄明白为什么它能控制得这么短 ,所以这一期先不聊这一块原理。

但是从跑的效果来看 ,很强!!!。

总结

关于 ZGC 我也只是尝试的非生产环境使用过 ,本篇文章也只是为了尝试一下它的性能优势。

由于本地服务器内存大小的限制 ,所以大内存的回收还不能进行尝试 ,一些特殊的参数这一期也没使用。

大概看了一下 ZGC 的相关文档 ,在内存处理 ,回收标记等阶段和以往的垃圾回收器都有很大的不同,时间有限没有进行太深入的了解 ,所以这一篇不会太复杂。

这一块后续会进行深度的学习。

参考文档 :

《新一代垃圾回收器ZGC设计与实现》