Java JVM OpenJDK12 Shenandoah 收集器

2023-10-27 19:59

本文主要是介绍Java JVM OpenJDK12 Shenandoah 收集器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

专栏原创出处:github-源笔记文件 ,github-源码 ,欢迎 Star,转载请附上原文出处链接和本声明。

Java JVM-虚拟机专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Java JVM-虚拟机

前言

最初 Shenandoah 是由 RedHat 公司独立发展的新型收集器项目,在 2014 年 RedHat 把 Shenandoah 贡献给了 OpenJDK,并推动它成为 OpenJDK 12 的正式特性之一。

意味着我们的 Oracle JDK 无法使用它。

目标是实现一种能在任何堆内存大小下都可以把垃圾收集的停顿时间限制在「十毫秒」以内的垃圾收集器。

该目标意味着相比 CMS 和 G1,Shenandoah 不仅要进行并发的垃圾标记,还要并发地进行对象清理后的整理动作。

与 G1 的关系

Shenandoah 像是 G1 的下一代继承者,它们两者有着相似的堆内存布局,在初始标记、并发标记等许多阶段的处理思路上都高度一致。

它与 G1 至少有三个明显的不同之处:

  1. 支持并发的整理算法,G1 的回收阶段是可以多线程并行的,但却不能与用户线程并发

  2. 默认不使用分代收集,不会有专门的新生代 Region 或者老年代 Region 的存在,没有实现分代,并不是说分代对 Shenandoah 没有价值,这更多是出于性价比的权衡,基于工作量上的考虑而将其放到优先级较低的位置上

  3. 摒弃了在 G1 中耗费大量内存和计算资源去维护的记忆集,改用名为「连接矩阵」(Connection Matrix)的全局数据结构来记录跨 Region 的引用关系,降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题

工作过程

名词解释:

  • Collection Set:回收集

  • Immediate Garbage Region:一个存活对象都没有找到的 Region

1. 初始标记(Initial Marking)

与 G1 一样,首先标记与 GC Roots 直接关联的对象,停顿时间与堆大小无关,只与 GC Roots 的数量相关。

第一个「Stop The World」。

2. 并发标记(Concurrent Marking)

与 G1 一样,遍历对象图,标记出全部可达的对象,这个阶段是与用户线程一起并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。由于应用程序可以在此阶段自由分配新数据,因此在并发标记期间堆占用率会上升。

3. 最终标记(Final Marking)

与 G1 一样,处理剩余的 SATB 扫描,并在这个阶段统计出回收价值最高的 Region,将这些 Region 构成一组回收集。

第二个「Stop The World」(短暂的停顿)。

4.并发清理(Concurrent Cleanup)

这个阶段用于清理「一个存活对象都没有找到的 Region」。

5. 并发疏散(Concurrent Evacuation)

将回收集里面的存活对象先复制一份到其他未被使用的 Region 之中。(这个阶段是与之前 HotSpot 中其他收集器的核心差异
并发疏散阶段运行的时间长短取决于回收集的大小。

并发进行时,复制对象动作通过读屏障和被称为「Brooks Pointers」的转发指针来解决。
转发指针:在原有对象布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己。

6. 初始引用更新(Initial Update Reference)

并发疏散阶段复制对象结束后,还需要把堆中所有指向旧对象的引用修正到复制后的新地址,这个操作称为引用更新。

引用更新的初始化阶段实际上并未做什么具体的处理,设立这个阶段只是为了建立一个线程集合点,确保所有并发疏散阶段中进行的收集器线程都已完成分配给它们的对象移动任务而已。

第三个「Stop The World」(短暂的停顿)。

7. 并发引用更新(Concurrent Update Reference)

真正开始进行引用更新操作,这个阶段是与用户线程一起并发的,时间长短取决于内存中涉及的引用数量的多少。

并发引用更新与并发标记不同,它不再需要沿着对象图来搜索,只需要按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为新值即可。

8. 最终引用更新(Final Update Reference)

解决了堆中的引用更新后,还要修正存在于 GC Roots 中的引用。

第四个「Stop The World」,停顿时间只与 GC Roots 的数量相关。

9. 并发清理(Concurrent Cleanup)

经过并发疏散和引用更新之后,整个回收集中所有的 Region 已再无存活对象,这些 Region 都变成 Immediate Garbage Regions 了,最后再调用一次并发清理过程来回收这些 Region 的内存空间,供以后新对象分配使用。

关键步骤示意图

下图为以下 3 个关键步骤的运行过程:

  • 并发标记(Concurrent Marking)
  • 并发疏散(Concurrent Evacuation)
  • 并发引用更新(Concurrent Update Reference)

实践分析

堆大小

与几乎所有其他 GC 的性能一样,Shenandoah 的性能取决于堆大小。

如果在并发阶段运行时有足够的堆空间来容纳分配,它应该会更好。

  • 对于某些实时数据集少,分配压力适中的工作负载,1 - 2 GB 堆的性能很好。

  • 对于高达 80% 存活对象大小的各种工作负载上,堆大小在 4 - 128 GB 时根据实际情况测试。

暂停

Shenandoah 的暂停行为主要由「GC Roots」操作控制:扫描和更新 Roots。
Roots 包括:局部变量,嵌入在生成的代码中的引用,中间字符串,来自类加载器的引用(例如静态引用),JNI 引用,JVMTI 引用。

拥有更大的「GC Roots」通常意味着对 Shenandoah 的停顿时间更长。

吞吐量

由于 Shenandoah 是并发 GC。
在大多数情况下,暂停时间在 0-10ms 之内,而吞吐量损失在 0-15%之内。实际的性能数字在很大程度上取决于实际的应用程序,配置等。

相关参数

     bool ShenandoahAcmpBarrier                    = true                                   {diagnostic} {default}bool ShenandoahAllocFailureALot               = false                                  {diagnostic} {default}uintx ShenandoahAllocSpikeFactor               = 5                                    {experimental} {default}intx ShenandoahAllocationStallThreshold       = 10000                                  {diagnostic} {default}uintx ShenandoahAllocationThreshold            = 0                                    {experimental} {default}bool ShenandoahAllocationTrace                = false                                  {diagnostic} {default}bool ShenandoahAllowMixedAllocs               = true                                   {diagnostic} {default}bool ShenandoahAlwaysClearSoftRefs            = false                                {experimental} {default}bool ShenandoahAlwaysPreTouch                 = false                                  {diagnostic} {default}bool ShenandoahCASBarrier                     = true                                   {diagnostic} {default}bool ShenandoahCloneBarrier                   = true                                   {diagnostic} {default}uintx ShenandoahCodeRootsStyle                 = 2                                    {experimental} {default}bool ShenandoahCommonGCStateLoads             = false                                {experimental} {default}bool ShenandoahConcurrentScanCodeRoots        = true                                 {experimental} {default}uintx ShenandoahControlIntervalAdjustPeriod    = 1000                                 {experimental} {default}uintx ShenandoahControlIntervalMax             = 10                                   {experimental} {default}uintx ShenandoahControlIntervalMin             = 1                                    {experimental} {default}uintx ShenandoahCriticalFreeThreshold          = 1                                    {experimental} {default}bool ShenandoahDecreaseRegisterPressure       = false                                  {diagnostic} {default}bool ShenandoahDegeneratedGC                  = true                                   {diagnostic} {default}bool ShenandoahDontIncreaseWBFreq             = true                                 {experimental} {default}bool ShenandoahElasticTLAB                    = true                                   {diagnostic} {default}uintx ShenandoahEvacAssist                     = 10                                   {experimental} {default}uintx ShenandoahEvacReserve                    = 5                                    {experimental} {default}bool ShenandoahEvacReserveOverflow            = true                                 {experimental} {default}double ShenandoahEvacWaste                      = 1.200000                             {experimental} {default}uintx ShenandoahFreeThreshold                  = 10                                   {experimental} {default}uintx ShenandoahFullGCThreshold                = 3                                    {experimental} {default}ccstr ShenandoahGCHeuristics                   = adaptive                             {experimental} {default}uintx ShenandoahGarbageThreshold               = 60                                   {experimental} {default}uintx ShenandoahGuaranteedGCInterval           = 300000                               {experimental} {default}size_t ShenandoahHeapRegionSize                 = 0                                    {experimental} {default}bool ShenandoahHumongousMoves                 = true                                 {experimental} {default}intx ShenandoahHumongousThreshold             = 100                                  {experimental} {default}uintx ShenandoahImmediateThreshold             = 90                                   {experimental} {default}bool ShenandoahImplicitGCInvokesConcurrent    = true                                 {experimental} {default}uintx ShenandoahInitFreeThreshold              = 70                                   {experimental} {default}bool ShenandoahKeepAliveBarrier               = true                                   {diagnostic} {default}uintx ShenandoahLearningSteps                  = 5                                    {experimental} {default}bool ShenandoahLoopOptsAfterExpansion         = true                                 {experimental} {default}uintx ShenandoahMarkLoopStride                 = 1000                                 {experimental} {default}intx ShenandoahMarkScanPrefetch               = 32                                   {experimental} {default}size_t ShenandoahMaxRegionSize                  = 33554432                             {experimental} {default}uintx ShenandoahMergeUpdateRefsMaxGap          = 200                                  {experimental} {default}uintx ShenandoahMergeUpdateRefsMinGap          = 100                                  {experimental} {default}uintx ShenandoahMinFreeThreshold               = 10                                   {experimental} {default}size_t ShenandoahMinRegionSize                  = 262144                               {experimental} {default}bool ShenandoahOOMDuringEvacALot              = false                                  {diagnostic} {default}bool ShenandoahOptimizeInstanceFinals         = false                                {experimental} {default}bool ShenandoahOptimizeStableFinals           = false                                {experimental} {default}bool ShenandoahOptimizeStaticFinals           = true                                 {experimental} {default}bool ShenandoahPacing                         = true                                 {experimental} {default}uintx ShenandoahPacingCycleSlack               = 10                                   {experimental} {default}uintx ShenandoahPacingIdleSlack                = 2                                    {experimental} {default}uintx ShenandoahPacingMaxDelay                 = 10                                   {experimental} {default}double ShenandoahPacingSurcharge                = 1.100000                             {experimental} {default}uintx ShenandoahParallelRegionStride           = 1024                                 {experimental} {default}uint ShenandoahParallelSafepointThreads       = 4                                    {experimental} {default}bool ShenandoahPreclean                       = true                                 {experimental} {default}bool ShenandoahReadBarrier                    = true                                   {diagnostic} {default}uintx ShenandoahRefProcFrequency               = 5                                    {experimental} {default}bool ShenandoahRegionSampling                 = true                                 {experimental} {command line}int ShenandoahRegionSamplingRate             = 40                                   {experimental} {default}bool ShenandoahSATBBarrier                    = true                                   {diagnostic} {default}uintx ShenandoahSATBBufferFlushInterval        = 100                                  {experimental} {default}size_t ShenandoahSATBBufferSize                 = 1024                                 {experimental} {default}bool ShenandoahStoreCheck                     = false                                  {diagnostic} {default}bool ShenandoahStoreValEnqueueBarrier         = false                                  {diagnostic} {default}bool ShenandoahStoreValReadBarrier            = true                                   {diagnostic} {default}bool ShenandoahSuspendibleWorkers             = false                                {experimental} {default}size_t ShenandoahTargetNumRegions               = 2048                                 {experimental} {default}bool ShenandoahTerminationTrace               = false                                  {diagnostic} {default}bool ShenandoahUncommit                       = true                                 {experimental} {default}uintx ShenandoahUncommitDelay                  = 300000                               {experimental} {default}uintx ShenandoahUnloadClassesFrequency         = 0                                    {experimental} {default}ccstr ShenandoahUpdateRefsEarly                = adaptive                             {experimental} {default}bool ShenandoahVerify                         = false                                  {diagnostic} {default}intx ShenandoahVerifyLevel                    = 4                                      {diagnostic} {default}bool ShenandoahWriteBarrier                   = true   

启动 GC-周期策略(Heuristics)

Heuristics 相关参数

    ccstr ShenandoahGCHeuristics                   = adaptive                             {experimental} {default}uintx ShenandoahInitFreeThreshold              = 70                                   {experimental} {default}uintx ShenandoahMinFreeThreshold               = 10                                   {experimental} {default}uintx ShenandoahAllocSpikeFactor               = 5                                    {experimental} {default}uintx ShenandoahGarbageThreshold               = 60                                   {experimental} {default}uintx ShenandoahFreeThreshold                  = 10                                   {experimental} {default}uintx ShenandoahAllocationThreshold            = 0                                    {experimental} {default}ccstr ShenandoahUpdateRefsEarly                = adaptive                             {experimental} {default}

Heuristics 主要用于告诉 Shenandoah 何时启动一个 GC-周期。 其中-XX:ShenandoahGCHeuristics=<name>用于选择不同的策略

  • adaptive:动态的 (默认),学习观察先前的 GC-周期,然后启动下一个 GC-周期

    • -XX:ShenandoahInitFreeThreshold 触发​​“学习”集合的初始阈值

    • -XX:ShenandoahMinFreeThreshold 触发 GC 的可用空间阈值

    • -XX:ShenandoahAllocSpikeFactor 保留堆大小因子

    • -XX:ShenandoahGarbageThreshold 区域标记为收集之前包含的垃圾百分比

  • static:静态的,根据堆占用率和分配压力决定启动 GC-周期

    • -XX:ShenandoahFreeThreshold 启动 GC-周期时可用堆的百分比

    • -XX:ShenandoahAllocationThreshold 设置自上一个 GC-周期以来,在启动新的 GC-周期之前分配的内存百分比

    • -XX:ShenandoahGarbageThreshold 区域标记为收集之前包含的垃圾百分比

  • compact:紧凑型,连续的,只要分配发生,就会连续运行 GC-周期,并在上一个周期结束后立即开始下一个周期。通常会产生吞吐量开销,但会最快的进行空间回收

    • -XX:ConcGCThreads 并发 GC 线程的数量(应减少,为用户线程使用)

    • -XX:ShenandoahAllocationThreshold 设置自上一个 GC-周期以来,在启动新的 GC-周期之前分配的内存百分比

  • passive:用于诊断,一旦可用内存用完,将触发「Stop The World」GC

  • aggressive:用于诊断,一直处于激活状态。尽快完成上一个后启动新的 GC-周期(有点类似“compact”)

失败模式

Shenandoah 这样的并发 GC,依赖于比应用程序分配的更快。如果分配压力很高,并且在 GC 运行时没有足够的空间来吸收分配,则最终会发生分配失败。Shenandoah 有一个优雅的降级模式:

  • Pacing:(<10 ms) Pacer 用于在 GC 不够快的时候去「暂停」正在分配对象的线程,当 GC 速度跟上来就解除对这些线程的「暂停」,「暂停」不是无期限的,取决于 ShenandoahPacingMaxDelay(单位毫秒) 参数,一旦超过该参数值就会取消「暂停」。
    当分配压力大的时候,Pacer 就无能为力了,这个时候就会进入下一个模式。
    -XX:+ ShenandoahPacing 默认启用

  • Degenerated GC:(<100 ms) 如果 GC-周期开始得太晚,或者发生了非常大的分配峰值,则可能会发生 Degenerated GC。在这个模式下,Shenandoah 使用的线程数取之于 ParallelGCThreads 而非 ConcGCThreads
    -XX:+ ShenandoahDegeneratedGC 默认启用

  • Full GC:(>100 ms)当 Degenerated GC 之后还没有足够的内存,则进入 Full GC 周期并将堆压缩到最大,它会尽可能地进行然后释放内存以确保不发生 OOM

参考

鉴于资料有限情况,本文章遗留较多问题,比如参数的含义、失败模式下 Degenerated GC 的处理过程,后续深入了解后修正补充。

  • 有关《 Java JVM JDK11 前的 7 个垃圾收集器》参考本专栏文章
  • 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第 3 版)》周志明 著
  • OpenJDK-Shenandoah
  • JDK12 ShenandoahGC小试牛刀
  • 可视化 ShenandoahGC 工具

这篇关于Java JVM OpenJDK12 Shenandoah 收集器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/288040

相关文章

springboot集成easypoi导出word换行处理过程

《springboot集成easypoi导出word换行处理过程》SpringBoot集成Easypoi导出Word时,换行符n失效显示为空格,解决方法包括生成段落或替换模板中n为回车,同时需确... 目录项目场景问题描述解决方案第一种:生成段落的方式第二种:替换模板的情况,换行符替换成回车总结项目场景s

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

SpringBoot中@Value注入静态变量方式

《SpringBoot中@Value注入静态变量方式》SpringBoot中静态变量无法直接用@Value注入,需通过setter方法,@Value(${})从属性文件获取值,@Value(#{})用... 目录项目场景解决方案注解说明1、@Value("${}")使用示例2、@Value("#{}"php

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

基于 Cursor 开发 Spring Boot 项目详细攻略

《基于Cursor开发SpringBoot项目详细攻略》Cursor是集成GPT4、Claude3.5等LLM的VSCode类AI编程工具,支持SpringBoot项目开发全流程,涵盖环境配... 目录cursor是什么?基于 Cursor 开发 Spring Boot 项目完整指南1. 环境准备2. 创建

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

springboot中使用okhttp3的小结

《springboot中使用okhttp3的小结》OkHttp3是一个JavaHTTP客户端,可以处理各种请求类型,比如GET、POST、PUT等,并且支持高效的HTTP连接池、请求和响应缓存、以及异... 在 Spring Boot 项目中使用 OkHttp3 进行 HTTP 请求是一个高效且流行的方式。

java.sql.SQLTransientConnectionException连接超时异常原因及解决方案

《java.sql.SQLTransientConnectionException连接超时异常原因及解决方案》:本文主要介绍java.sql.SQLTransientConnectionExcep... 目录一、引言二、异常信息分析三、可能的原因3.1 连接池配置不合理3.2 数据库负载过高3.3 连接泄漏