当前位置: 首页 > article >正文

【JVM】—深入理解ZGC回收器—关键技术详解

深入理解ZGC回收器—关键技术详解

⭐⭐⭐⭐⭐⭐
Github主页👉https://github.com/A-BigTree
笔记链接👉https://github.com/A-BigTree/Code_Learning
⭐⭐⭐⭐⭐⭐

如果可以,麻烦各位看官顺手点个star~😊

文章目录

  • 深入理解ZGC回收器—关键技术详解
    • 1 关键技术1—堆内存布局
      • 1.1 G1的内存布局
      • 1.2 ZGC的内存布局
    • 2 关键技术2🌟—指针着色技术Colored Pointers
      • 2.1 具体实现
      • 2.2 GC中的应用
    • 3 关键技术3—读屏障
    • 4 总结


关于JVM回收器的前置知识点:

  • 【JVM】—Java内存区域详解
  • 【JVM】—JVM垃圾回收详解
  • 【JVM】—深入理解G1回收器——概念详解
  • 【JVM】—深入理解G1回收器—回收过程详解
  • 【JVM】—G1中的Young GC、Mixed GC、Full GC详解
  • 【JVM】—G1 GC日志详解
  • 【JVM】—深入理解ZGC回收器—背景概念&回收流程

前面介绍了ZGC的诞生背景和回收流程(传送门),这篇文章从内存布局、着色指针和读屏障三个角度出发,详细介绍一下ZGC用到的关键技术。

1 关键技术1—堆内存布局

1.1 G1的内存布局

让我先回顾一下G1的内存布局如下:

在这里插入图片描述

G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

1.2 ZGC的内存布局

类似的,ZGC中没有了分代的概念(新生代、老年代),内存布局如下图所示:

在这里插入图片描述

ZGC支持3种页面(ZPages),分别为小页面、中页面和大页面。其中小页面指的是2MB的页面空间,中页面指32MB的页面空间,大页面指受操作系统控制的大页。

  • 当对象大小小于等于256KB时,对象分配在小页面;
  • 当对象大小在256KB和4M之间,对象分配在中页面;
  • 当对象大于4M,对象分配在大页面;

ZGC对于不同页面回收的策略也不同。 简单地说,小页面优先回收;中页面和大页面则尽量不回收。 同时ZGC 的物理堆区域可以映射到更大的堆地址空间(可以包括虚拟内存),这对于解决内存碎片问题至关重要

想象一下,用户想要在堆内存中分配一个非常大的对象,但由于内存中没有连续的空间,这通常会需要多个 GC 周期来释放足够的连续空间。而且如果GC后还没有可用空间,,JVM 就是抛出OutOfMemoryError。但是由于物理内存映射到更大的地址空间,找到更大的连续空间对于ZGC是可行的。

2 关键技术2🌟—指针着色技术Colored Pointers

在 ZGC 中,Colored Pointers 主要用于解决并发垃圾回收过程中的指针更新问题。ZGC 使用了一种称为 指针压缩 的技术,其中指针的一部分位被用来存储额外的信息,以便于垃圾回收器进行高效的并发操作。

以下是颜色指针的数据结构:

在这里插入图片描述

在堆中指针指引结构如下:

在这里插入图片描述

具体来说,ZGC 使用了以下几种状态来标记指针:

  • 未标记(Unmarked): 指针没有任何特殊标记,表示该对象还没有被垃圾回收器处理。
  • 标记(Marked): 指针被标记为已处理,表示该对象已经被垃圾回收器发现并处理过。
  • 重定位(Relocated): 指针被标记为已重定位,表示该对象已经被移动到新的内存位置,并且指针指向了新的位置。

这些状态通常通过指针的最低几位来实现。例如,ZGC 可能会使用指针的最低两位来存储这些状态信息。

2.1 具体实现

  • 未标记(Unmarked):指针的最低两位为 00。表示该对象还没有被垃圾回收器处理。
  • 标记(Marked):指针的最低两位为 01。表示该对象已经被垃圾回收器发现并处理过。
  • 重定位(Relocated):指针的最低两位为 10。表示该对象已经被移动到新的内存位置,并且指针指向了新的位置。

并发垃圾回收过程

  1. 标记阶段:
    • 垃圾回收器遍历对象图,将发现的对象标记为 Marked。
    • 如果对象需要被移动,垃圾回收器会将其标记为 Relocated,并将指针更新为新位置。
  2. 更新指针:
    • 在并发阶段,应用程序线程继续运行,可能会修改对象图。
    • 垃圾回收器需要确保在更新指针时不会丢失对已移动对象的引用。
  3. 清除阶段:
    • 垃圾回收器清理未被标记的对象,释放内存。
    • 通过这种方式,ZGC 能够在并发垃圾回收过程中高效地管理和追踪对象引用,减少停顿时间并提高整体性能。

2.2 GC中的应用

首先是初始阶段,使用蓝色来表示Remapped

在这里插入图片描述

接下来我们以M0区域表示第一次GC的指针染色,根据我们熟知的GC的根可达算法,将指针标记为绿色,表示此次我们GC存活的对象指针是用绿色表示

在这里插入图片描述

接下来就开始执行转移,可以看到使用了复制算法,将对象移动到了新的页,然后开始了初始阶段我们的 GCROOT也会指向新的页 ,这时候还没有进行指针标记,那么会通过一个转发表的数据结构,在新的页中,来指向我们旧页的对象,这样的话就会成功引用到我们即将保留的对象。

在这里插入图片描述

接下来就清空旧页的对象将被回收的对象

在这里插入图片描述

此时已经到达 第二轮GC标记 ,那么此次M0为红色,那么就会判断上次GC的绿色指针进行并发标记,此次标记为红色表示次轮GC的存活对象指针标识,最后清空旧页的指针以及转发表等数据

在这里插入图片描述

由此可以看出,ZGC是经历了两轮GC才会真正的将垃圾清除,通过颜色指针的M0与M1交替标记,来通过根可达算法标识存活对象,在整个过程可以看到,STW的时间点在初始标记,再标记,以及初始转移,这些动作仅仅与GCROOT的头节点的对象有关,所以标记以及转移动作特别快,然后大批量的标记和转移都是并发的,所以整体STW时间停顿特别少,而且回收的过程又是复制算法,所以非常高效。具体回收过程如下图所示:

在这里插入图片描述

3 关键技术3—读屏障

之前的GC都是采用写屏障(Write Barrier),而ZGC采用的是读屏障。读屏障(Load Barriers)类似于 Spring AOP 的前置通知。

在ZGC中,当读取处于重分配集的对象时,会被读屏障拦截,通过转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为叫做指针的「自愈能力」。这样就算GC把对象移动了,读屏障也会发现并修正指针,于是应用代码就永远都会持有更新后的有效指针,而且不需要STW,类似JDK里的 CAS 自旋,读取的值发现已经失效了,需要重新读取。

好处是:第一次访问旧对象访问会变慢,但也只会有一次变慢,当「自愈」完成后,后续访问就不会变慢了。

正是因为Load Barriers的存在,所以会导致配置ZGC的应用的吞吐量会变低。不过这点开销是值得的。

读屏障示例:

Object o = obj.FieldA   // 从堆中读取引用,需要加入屏障
<Load barrier>
Object p = o  // 无需加入屏障,因为不是从堆中读取引用
o.dosomething() // 无需加入屏障,因为不是从堆中读取引用
int i =  obj.FieldB  //无需加入屏障,因为不是对象引用

4 总结

相比G1、Shenandoah等先进的垃圾收集器,ZGC在实现细节上做了一些不同的权衡选择。

譬如G1需要通过写屏障来维护记忆集,才能处理跨代指针,得以实现Region的增量回收。记忆集要占用大量的内存空间,写屏障也对正常程序运行造成额外负担,这些都是权衡选择的代价。

ZGC就完全没有使用记忆集,它甚至连分代都没有,连像CMS中那样只记录新生代和老年代间引用的卡表也不需要,因而完全没有用到写屏障,所以给用户线程带来的运行负担也要小得多。

可是,有优就有劣,ZGC的这种选择也限制了它能承受的对象分配速率不会太高。

因为ZGC四个阶段都支持并发,如果分配速率高,将创造大量的新对象,这就产生了大量的浮动垃圾。如果这种高速分配持续维持的话,回收到的内存空间持续小于期间并发产生的浮动垃圾所占的空间,堆中剩余可腾挪的空间就越来越小了。

目前唯一的办法就是尽可能地增加堆容量大小,获得更多喘息的时间。但是若要从根本上提升ZGC能够应对的对象分配速率,还是需要引入分代收集,让新生对象都在一个专门的区域中创建。所以分代算法有利有弊。


http://www.kler.cn/news/360356.html

相关文章:

  • M3D: Advancing 3D Medical Image Analysis with Multi-Modal Large Language Models
  • Scala内部类和Java内部类的不同
  • TCP/IP 寻址
  • 力扣面试150 完全二叉树的节点个数 树的高度
  • Python酷库之旅-第三方库Pandas(148)
  • 基于vue框架的的地铁站智慧管理系统的设计n09jb(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
  • Java:IDEA生成JavaDoc文档
  • 2024年双十一最建议买的东西,女生双十一必买清单,双11好物推荐
  • Git使用(什么是工作区、暂存区、本地库、远程库、Pycharm部署)
  • 【C++篇】深度解析类与对象(中)
  • ASP.NET.Web应用程序(.NET Framework)添加Swagger本地Debuge成功打开接口展示界面,发布服务器无法打开接口展示界面
  • 【ChatGPT】如何限定 ChatGPT 的回答范围
  • 网络资源模板--Android Studio 实现背单词App
  • 计算机毕业设计 基于Python的社交音乐分享平台的设计与实现 Python毕业设计 Python毕业设计选题【附源码+安装调试】
  • 【网络安全】IDOR与JWT令牌破解相结合,实现编辑、查看和删除数万帐户
  • 每天5分钟玩转C#/.NET之了解C#中的顶级语句
  • Go语言依赖注入方式
  • 高效容器化技术(2)---docker的安装
  • 实用好助手
  • 【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (一):项目简介及安装依赖