👈👈👈 欢迎点赞收藏关注哟
首先分享之前的所有文章 >>>> 😜😜😜
文章合集 : 🎁 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
2.2 默认回收器性能 (Parallel)
// 基于 JDK 8
-XX:+UnlockExperimentalVMOptions
-Xms512m
-Xmx2048m
默认回收器 (Parallel Scavenge + Parallel Old)
这里我使用的是 OpenJDK 8u412 版本 ,此版本里面新生代和老年代回收都走的 Parallel ,我们可以明显看到以下特性 :
- 整体回收的节奏完全不一样 (前半部分是 G1 的数据效果 ,后面是 Parallel 的执行效果)
三. ZGC 切换体验
- 关于 ZGC 的更底层的原理就不在这一篇来学习了 ,我们主要从体验上来了解一下它。
3.1 来回顾一下 ZGC
ZGC 提出的最大的3个目标 :
- 低停顿时间 :不管多大的堆 ,将垃圾回收的停顿时间控制在 10 ms 以内
- 大堆支持 : 可以支持特别大的堆 ,TB 级别的数据量
- 并发处理 : 垃圾回收过程中除了个别操作 ,极大部分都是并发进行的
3.2 ZGC 常规性能
- 单纯从跑的结果来看 ,JVM 堆的回收变化是很大的
- 触发的各种垃圾回收的行为变得更加频繁
回收的时间杂乱无章,参数变得更多了 ,他们分别表示什么呢?
❓为什么这里的 Pause Durations 指标超过了 10ms ?
我知道肯定很多小伙伴会问这个问题 ,为什么 ZGC 都说了 ,停顿时间 10ms 以内 ,这里看起来明显不止。
这里我们要继续回顾一个概念 ,不管是 ZGC 还是 G1 , 其控制的这个停顿时间 ,都是期望停顿时间。
同时这个图表里面分为 GC 结束时间和 GC 暂停时间。 整个 GC 的处理时间是很长的 ,但是 STW 的时间没想象的那么多长 : (从下面图片能看到 ,暂停的时间其实很短)
- 所以这里为了能更清晰的了解 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
后文我们会讲到 ,这里先知道 ,ZGC 有3个 STW 的阶段 : 初始标记,再标记,初始转移
- 初始标记 : Pause Mark Start 0.021ms
- 再标记 :Pause Mark End 0.023ms
- 初始转移 : Pause Relocate Start 0.006ms
而时间段上面的最大耗时也不大 :
好了 ,收 ,这一块这一期就看这么多,这些参数有很多我还没深入,就不细说了
更加直观的感受 :
- 👉 回收频率侧参数 (左侧) :
参数 | 描述 |
---|---|
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 整个算法的流程 :
- 初始标记 : 从根集合出发,找出根集合直接引用的活跃对象,并入栈
- 并发标记 : 根据初始标记的结果 ,作为并发标记的根对象,遍历对象的成员变量进行标记
- 再标记和非强根并行标记 :由于并发标记是并行的,理论上存在引用表达影响结果 ,这一步则会判断是否可以结束标记 。 如果存在引用变化 ,则进行并行标记。
- 并发处理非强引用和非强根并发标记 : 对定义了finalize()函数的对象需要特殊处理 。 同时对一些要在 STW 中执行的操作预先并行执行
- 重置转移集合中的页面
- 回收无效的页面
- 并发选择对象的转移集合
- 并发初始化转移集合中的每个页面
- 初始转移根对象引用的对象
- 并发转移
这里还有一个小细节 ,可能大家会注意到 ,转移后并没有再触发回收! .
这是因为转移完成后可回收的页面是在下一次垃圾回收周期回收的。
也就是每次算法的第六步。
除了这些 ,我们先不考虑每个过程做了什么。 其实整个流程就是这样 :
- 对象被创建 👉 可以被回收了 👉 被标记 👉 先把不需要回收的进行转移 👉 留下要回收的在无效页 👉 下一次回收
4.5 停顿时间是否达到了预期 ?
ZGC只有三个STW阶段:初始标记,再标记,初始转移。
那么除了这3个阶段 ,来看看其他阶段的性能效果 :
- 并发标记 / 并发定位 :通过并发进行的方式 ,避免了 STW 的产生
- 在并发环节中对一些 STW 的操作进行预处理 , 避免了后续耗时
- 支持多线程处理 ,提高回收的效率
具体还有一些其他的策略 ,从这些简单的描述里面 ,我其实没弄明白为什么它能控制得这么短 ,所以这一期先不聊这一块原理。
但是从跑的效果来看 ,很强!!!。
总结
关于 ZGC 我也只是尝试的非生产环境使用过 ,本篇文章也只是为了尝试一下它的性能优势。
由于本地服务器内存大小的限制 ,所以大内存的回收还不能进行尝试 ,一些特殊的参数这一期也没使用。
大概看了一下 ZGC 的相关文档 ,在内存处理 ,回收标记等阶段和以往的垃圾回收器都有很大的不同,时间有限没有进行太深入的了解 ,所以这一篇不会太复杂。
这一块后续会进行深度的学习。
参考文档 :
《新一代垃圾回收器ZGC设计与实现》