「查漏补缺」ZGC相关内容整理

  • 年更博主,前来报到🐶。最近发生了一个频繁oom的问题,经过排查和zgc有点关系。zgc之前没有怎么接触过,借此机会也研究了下。(具体原因就没法在这写了)
  • 这篇文章更多的是ZGC资料的汇总,方便快速回顾。
  • 英语水平有限,外加JDK21后出现分代特性,文章较少,如有错误,欢迎指出。参考内容在末尾!!!

简介

Z Garbage Collector(ZGC):ZGC是一个可拓展的低延迟的垃圾收集器。ZGC可以在不停止应用程序线程执行超过一毫秒的情况下,并发完成所有耗时的工作。ZGC可以很好地支持从几百兆字节到 16TB 的堆大小。

ZGC从JDK 11 引入,JDK 15开始可以用于生产环境,JDK 21被重新实现以支持分代。

ZGC垃圾回收过程

以上是美团整理的垃圾回收过程。

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

  • 初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;
  • 再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。

Mark Phase

这个不分大致分为3个阶段

  1. 初始标记:STW 阶段,从GC Roots 标记他们可以访问到的对象。
  2. 并发标记:上一个阶段STW阶段结束后,会并发遍历、标记
  3. 再标记:遍历完成后,会有一个最后的、非常短暂的STW,用于处理一些边界情况,随后标记过程就结束了。

Relocate Phase

并发转移准备:ZGC将堆划分为多个页面(page),并在该阶段开始时并发地选择一组页面--有存活对象且需要被重新分配。

初始转移:STW,在此期间ZGC会转移GC Root,将它们的引用重新映射到新的地址。

并发转移:此阶段是并发的,这就有一个问题,如果用户线程访问到转移后的对象怎么办,这个就是通过加载屏障(Load Barrier)来实现的。

着色指针(Pointer colouring)

着色指针:核心思想是空间换时间。指针大小为64位,其中内存地址是42为,其他22位可以用来存储信息。目前用了以下4位

  • Finalizable:是否可以被回收(JDK18启用)
  • Remapped: 表示该引用需要被重定向
  • Marked1&Marked0:是否被GC标记,每个GC周期会交替使用这2个标记

加载屏障(Load Barrier)

承接上文说的,如果在并发转移(Relocation)的过程中,用户线程访问到了被转移的对象,此时加载屏障(Load Barrier)开始介入,保证引用始终指向有效对象。

  1. 先判断标记位------Remapped 检查引用指针的高位是否设置了Remapped标志位。

如果设置了,表示该对象可能已经被转移但引用尚未更新,需要更新引用。

  1. 判断是否已经被转移 加载旧地址的对象头,检查是否是在 Forwarding Stub(转发表)中。 如果存在,说明对象已经迁移,引用需要被更新。 如果不存在,什么也不做,保留引用。

存储屏障

这个是分代ZGC特有,主要是用来跟踪跨代引用。ZGC在着色指针中添加标记位,以便存储存储屏障能够判断是否包含跨代指针。

其他特性

  • 无多重映射内存(No multi-mapped memory)
    • 将非分代zgc的多重映射内存取消了,更好的检测堆内存使用量(实际使用量3倍的问题)
  • 优化屏障(Optimized barriers)
    • Fast paths and slow paths
    • Minimizing load barrier responsibilities
    • Remembered-set barriers
    • SATB marking barriers
    • Fused store barrier checks
    • Store barrier buffers
    • Barrier patching
  • 双重缓冲记忆集合(Double-buffered remembered sets)
    • 使用位图代替卡表(512位,每一位标记一段区域是否被写过)形式
    • 一个位图(bitmap)用于活跃线程标记,另一个用于gc
    • 每次young gc,交换2个位图,gc处理老位图
  • 重新分配无需额外堆内存(Relocations without additional heap memory)
    • 2阶段gc,第一阶段标记可达对象,二阶段分region重定位
  • 密集的堆区域(Dense heap regions)
    • region使得对象更密集,zgc会分析年轻代region的密度,判断哪些更值得清理。不需要清理的区域直接接地老化
  • 大对象(Large objects)
    • ZGC 已经能够很好地处理大型对象。通过将虚拟内存与物理内存解耦,并预留过多的虚拟内存,ZGC 通常可以避免使用 G1 时导致大型对象分配困难的碎片问题。
  • 完整的垃圾收集(Full garbage collections)
    • 如果 GC 是由显式调用(如 System.gc())触发的,ZGC 会在旧生代 GC 前执行一次额外的年轻代 GC,并强制将存活对象提升到旧生代,确保引用处理和类卸载能正常发生。

分代ZGC

分代ZGC JDK21 发布的新特性,旨在降低分配停滞(allocations stalls)的风险,减少所需的堆内存开销,并降低垃圾收集的CPU开销。与非分代 ZGC 相比,这些优势预计不会显著降低吞吐量。非分代 ZGC 的基本特性(例如,暂停时间不超过 1 毫秒,以及支持从几百兆字节到数 TB 的堆大小)将得以保留。

分代 ZGC 基于弱分代假说,即年轻对象往往会在年轻时死去,而老对象往往会保留下来。通过更频繁地收集年轻对象,ZGC 可以提高应用程序的性能。

分代 ZGC 引入了多个使其不同于非分代 ZGC 和其他垃圾收集器的设计概念,包括无多重映射内存、优化屏障、双缓冲记忆集、无追加堆内存重定位、密集堆区域、大对象和完整垃圾收集。

分代 ZGC 的引入可以大幅提升在 Java 平台上运行应用程序的性能。通过更频繁地收集年轻对象,分代 ZGC 可以带来更低的延迟、更少的内存开销和更高的 CPU 利用率。这使得它在大多数用例中都成为比非分代 ZGC 更好的解决方案。

JDK24移除了非分代模式。

内存归还问题

  • commit 内存: 向操作系统申请并保留的物理内存,jvm随时使用
  • uncommit 内存: jvm预留了,但是没有实际占用。操作系统可以拿来用

zgc默认情况下是uncommit 内存,可以使用 -XX:-ZUncommit 禁用这个特性,如果-Xmx 和-Xms设置的一样,也会被隐式禁用。

可以配置 -XX:ZUncommitDelay=(默认300s),设置延迟uncommit 内存的延迟时间

但是此处很奇怪,当系统rss大道limit的时候,且xmx=xms,再监控上,我观察到,jvm释放了一部分内存,暂时没有找到任何文献,有知道的欢迎指教。

ZGC相关调优参数

通用调参

参数 注释
-Xms 最小堆大小
-Xmx 最大堆大小
-XX:SoftMaxHeapSize ZGC 的软上限,GC 会尽量控制堆不超过这个值(非硬限制)
-XX:ConcGCThreads 并发 GC 阶段使用的线程数(如并发标记)
-XX:ParallelGCThreads Stop-The-World 阶段使用的并行线程数(如初始标记)
-XX:+UseDynamicNumberOfGCThreads 根据系统负载动态调整 GC 线程数
-XX:+UseLargePages 启用大页内存(HugePages,手动配置)
-XX:+UseTransparentHugePages 启用透明大页(由操作系统自动分配)
-XX:+UseNUMA 在 NUMA 架构下启用内存亲和性优化
-XX:SoftRefLRUPolicyMSPerMB 每 MB 空闲堆内存对应的 SoftReference 最小保留时间(毫秒)
-XX:AllocateHeapAt=<路径> 将堆内存分配到指定挂载路径(如持久内存或大页挂载点)

ZGC特有

参数 注释
-XX:ZAllocationSpikeTolerance=<百分比> 忍受内存分配突增的阈值,超过该值可能触发 GC,默认约 2%
-XX:ZCollectionInterval=<秒> 主动 GC 的最小间隔时间(节流用)
-XX:ZFragmentationLimit=<百分比> 堆碎片率超过该阈值时触发 GC,默认 25%
-XX:ZMarkStackSpaceLimit=<字节> 标记阶段内部栈最多可使用的内存大小
-XX:+ZProactive 启用主动 GC,即使没有明显内存压力也会周期收集
-XX:+ZUncommit / -XX:-ZUncommit 开启/关闭自动释放未使用堆内存(返还给操作系统)
-XX:ZUncommitDelay=<秒> 堆内存空闲多久后可被释放,默认 300 秒

ZGC诊断

参数 注释
-XX:ZStatisticsInterval=<秒> 设置 GC 统计信息的输出间隔,默认 10 秒
-XX:+ZVerifyForwarding 验证对象转发指针的正确性(调试用)
-XX:+ZVerifyMarking 验证标记阶段是否完整(调试用)
-XX:+ZVerifyObjects 验证堆中对象的结构完整性(调试用)
-XX:+ZVerifyRoots 验证 GC 根集合的准确性(调试用)
-XX:+ZVerifyViews 验证 ZGC 内部视图映射关系是否正确(调试用)
-XX:ZYoungGCThreads 年轻代 GC 所使用的线程数(仅 ZGC 有效)
-XX:ZOldGCThreads 老年代 GC 所使用的线程数(仅 ZGC 有效)
-XX:+ZBufferStoreBarriers 启用写屏障缓冲(用于优化写屏障性能)

参考文献

相关推荐
Resean022311 分钟前
SpringMVC 6+源码分析(二)DispatcherServlet实例化流程 1
java·spring boot·spring·servlet·springmvc
泉城老铁37 分钟前
Spring Boot 对接阿里云 OSS 的详细步骤和流程
java·后端·程序员
Aurora_NeAr41 分钟前
大数据之路:阿里巴巴大数据实践——元数据与计算管理
大数据·后端
喜欢板砖的牛马43 分钟前
容器(docker container):你需要知道的一切
后端·docker
lichenyang4531 小时前
从零开始学Express,理解服务器,路由于中间件
后端
EnigmaGcl1 小时前
领域驱动设计,到底在讲什么?
后端·架构
丘山子1 小时前
API Gateway 工作原理介绍
前端·后端·面试
陈平安安1 小时前
Maven学习
java·maven
砌玉成璧1 小时前
Flask一个用户同时只能在一处登录实现
后端·python·flask
-$_$-1 小时前
【笔试真题】2024秋招京东后端开发岗位-第一批笔试
java·开发语言