Shenandoah GC---低延迟垃圾收集器

2023-10-27 19:59

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

相关概念

历史: Shenandoah GC是由 RedHat公司开发的的新型收集器,14年RedHat把Shenandoah贡献给了OpenJDK。
设计目标:实现一种能在任何堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的垃圾收集器。

与G1 GC的比较

相同点

  • 同样基于Region的堆内存布局,同样有着用于存放大对象Humongous Region
  • 默认的回收策略也同样是优先处理回收价值最大的Region

不同点

堆内存管理方面

  • 支持并发的整理算法,G1的回收阶段可以是并行的,但却不能与用户线程并发
  • 默认不使用分代收集,不会有专门的新生代Region或者老年代Region
  • 去除了G1中耗费内存和计算资源去维护的RSet,改为使用名为“连接矩阵”的全局数据结构来记录跨Region的引用,
    • 优点:
      • 降低了处理跨代指针时的记忆集维护消耗
      • 降低了伪共享的发生概率(伪共享相关内容:https://www.yuque.com/lihongjian/fx3n4r/cnhs3h)
    • 连接矩阵: 可以理解为一张二维表格,如果Region N有对象指向Region M,就在表格的N行M列上打上一个标记**,**如果Region5中的对象Baz引用了Region3的Foo,Foo又引用了Region1 的Bar,那连接矩阵中的5行3列、3行1列就应该被打上标记,回收时通过这张表格就可以得出哪些Region之间产生了跨代引用。

用户态、核心态相关概念:https://zh.wikipedia.org/wiki/




运行过程

1、2、3阶段与G1相同

  1. 初始标记(STW)

标记所有GC Roots能直接关联到的对象,速度很快,会造成短暂的暂停,时间长短与堆内存的大小无关,与对象的多少无关,与存活对象多少也无关

  1. 并发标记

GC线程与用户线程并发执行,遍历整个对象图,标记出所有可达的对象,时间长短取决于存活对象的数量以及对象图的复杂度

  1. 最终标记(STW)

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

  1. 并发清理

将标记到没有对象存活的Region清理掉

  1. 并发回收
    1. 在CMS中会删除不再使用的对象,并回收他们占用的内存
    2. 在G1中的拷贝存活对象(evacuiation)阶段是筛选出部分Region后拷贝到新的Region中去,并将原有的Region清空。
    3. Shenandoah GC要把回收集里面的存活对象先复制一份到其他未被使用的Region中。
      1. **问题:由于与用户线程并发执行,用户线程会对对象不停的进行读写操作,但移动对象后整个内存中所有指向该对象的引用都需要改变,总不能把所有指向该对象的引用全都修改一遍,那属实是太麻烦了?**Shenandoah使用读屏障和被称为“Brooks Pointer”的转发指针来解决,下文会进行解释
  2. 初始引用更新
    1. 前一阶段已经复制完对象了,理论上要更新引用了是吧,但事实却不是这样的,这个阶段其实是建立一个线程的集合点,由于GC是多线程执行的,需要一个集合点来保证所有的GC线程都把自己负责的对象复制完了。
  3. 并发引用更新
    1. 真正开始进行引用更新操作,此阶段不需要和并发标记阶段一样遍历整个对象图来搜索,只需要按照内存屋里地址的顺序,线性地搜索出引用类型,把旧值改为新值即可(其实只是涉及修改被移动对象的对象头的转发指针的引用,下文会讲到),此阶段的时间长短与存活对象的个数有关
  4. 最终引用更新 (STW)
    1. 修正存在于GC Roots中的引用,停顿时间只与GC Roots数量有关,因为GC Roots不一定参与回收,因此需要更新对应的引用,否则下次GC时对象就不可达了
  5. 并发清理
    1. 经过上边的阶段,整个回收集中所有的Region中已经再无存活对象,这些Region都变成直接垃圾区,因此最后再调用一次并发清理过程来回收这些Region的内存空间,供以后新对象分配使用。

Brooks Pointer/转发指针

Brooks Pointer/转发指针旨在实现对象移动与用户程序并发的解决方案。

之前的处理方式

在这种概念出现之前,要做类似的并发操作,通常是在被移动对象原有的内存上设置保护陷阱(Memory Protection Trap),一旦用户程序访问到归属于旧对象的内存空间就会产生自陷中段,进入预设好的异常处理器中,再由其中的代码逻辑把访问转发到复制后的新对象上
缺点: 虽然能实现对象移动与用户线程并发, 但是如果没有操作系统层面的直接支持,这种方案将导致用户态频繁切换到内核态,代价是非常大的,不能频繁使用。

Brooks Pointer/转发指针的处理方案

在原有的对象布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己。
在这里插入图片描述

是不是与对象的访问方式中的“句柄”模式有点像,两者都是间接性的对象访问方式,差别是句柄通常会统一存储所在专门的句柄池中,而转发指针是分散存放在每一个对象头前面。


优点

当对象拥有了一份新的副本时(复制),只需要修改旧对象上转发指针的引用位置,使其指向新对象,便可以将所有对该对象的访问转发到新的副本上,只有旧对象的内存仍然存在,未被清理掉,虚拟机的内存中所有通过旧引用地址访问的代码便仍然可以使用。

缺点

与其他间接对象访问技术的缺点相同,每次访问对象都会带来一次额外的转向开销(尽管这个开销已经被优化到只有一行汇编指令的程度),由于对象访问会被频繁使用到,仍然是一笔不可忽视的执行成本,与内存保护陷阱的方案比起来好了很多。

多线程竞争问题


并发读: 当垃圾收集线程与用户线程并发读取对象内容时,无论是读到旧地址对象还是读到新地址对象,返回的内容都是一致的,因此并发读没问题。

并发写: 对对象的写操作无疑有如下三种
(1)收集器线程复制出了对象新的副本
(2)用户线程更新对象的某个字段
(3)收集器更新对象的转发指针的地址为新地址

想象一下当(1)执行了,(3)执行之前(2)来执行,是不是会出现用户线程的更新更新到了旧的对象上,此时旧的对象还没被清理,此时一切还是正常,但是等到旧对象被回收掉后,是不是就有问题了,因此必须保证对转发指针的同步,Shenandoah采用的是CAS的操作来保证的。

执行频率问题

Shenandoah通过设置读写屏障来处理转发指针,对象的读动作是很频繁的,远远大于写动作(前边介绍的GC都只是使用了写屏障,Shenandoah是第一款介绍的使用的读屏障的GC)

  • 读频率高,自然读屏障的个数要远大于写屏障
  • 在之前垃圾收集器需要的写屏障的地方又加了转发操作


计划在JDK13中将Shenandoah的内存屏障模型改进为基于引用访问屏障,即: 指内存屏障只拦截对象中数据类型为引用类型的读写操作,不管原生数据类型等其他非引用字段的读写,这能够省去大量对原生对象、对象比较、对象加锁等场景中设置内存屏障带来的消耗。

这篇关于Shenandoah GC---低延迟垃圾收集器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

解读GC日志中的各项指标用法

《解读GC日志中的各项指标用法》:本文主要介绍GC日志中的各项指标用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、基础 GC 日志格式(以 G1 为例)1. Minor GC 日志2. Full GC 日志二、关键指标解析1. GC 类型与触发原因2. 堆

使用Python实现Windows系统垃圾清理

《使用Python实现Windows系统垃圾清理》Windows自带的磁盘清理工具功能有限,无法深度清理各类垃圾文件,所以本文为大家介绍了如何使用Python+PyQt5开发一个Windows系统垃圾... 目录一、开发背景与工具概述1.1 为什么需要专业清理工具1.2 工具设计理念二、工具核心功能解析2.

golang实现延迟队列(delay queue)的两种实现

《golang实现延迟队列(delayqueue)的两种实现》本文主要介绍了golang实现延迟队列(delayqueue)的两种实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录1 延迟队列:邮件提醒、订单自动取消2 实现2.1 simplChina编程e简单版:go自带的time

Spring框架中@Lazy延迟加载原理和使用详解

《Spring框架中@Lazy延迟加载原理和使用详解》:本文主要介绍Spring框架中@Lazy延迟加载原理和使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、@Lazy延迟加载原理1.延迟加载原理1.1 @Lazy三种配置方法1.2 @Component

JVM垃圾回收机制之GC解读

《JVM垃圾回收机制之GC解读》:本文主要介绍JVM垃圾回收机制之GC,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、死亡对象的判断算法1.1 引用计数算法1.2 可达性分析算法二、垃圾回收算法2.1 标记-清除算法2.2 复制算法2.3 标记-整理算法2.4

MySQL主从同步延迟问题的全面解决方案

《MySQL主从同步延迟问题的全面解决方案》MySQL主从同步延迟是分布式数据库系统中的常见问题,会导致从库读取到过期数据,影响业务一致性,下面我将深入分析延迟原因并提供多层次的解决方案,需要的朋友可... 目录一、同步延迟原因深度分析1.1 主从复制原理回顾1.2 延迟产生的关键环节二、实时监控与诊断方案

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

java实现延迟/超时/定时问题

《java实现延迟/超时/定时问题》:本文主要介绍java实现延迟/超时/定时问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java实现延迟/超时/定时java 每间隔5秒执行一次,一共执行5次然后结束scheduleAtFixedRate 和 schedu

Redis实现延迟任务的三种方法详解

《Redis实现延迟任务的三种方法详解》延迟任务(DelayedTask)是指在未来的某个时间点,执行相应的任务,本文为大家整理了三种常见的实现方法,感兴趣的小伙伴可以参考一下... 目录1.前言2.Redis如何实现延迟任务3.代码实现3.1. 过期键通知事件实现3.2. 使用ZSet实现延迟任务3.3