本文部分摘自《深入理解 Java 虚拟机第三版》

概述

衡量垃圾收集器的三项指标分别是:内存占用、吞吐量和延迟。这三者共同构成一个“不可能三角”,即一款优秀的收集器最多可以同时达成其中两项

随着硬件性能的提升,对内存占用和吞吐量也有所助益,但对延迟却并非如此。比如内存扩大了,对延迟反而会带来负面效果,因为回收 1TB 的堆内存毫无疑问会比回收 1GB 的堆内存耗费更多时间。因此,延迟成为了垃圾收集器最重视的性能指标

在 CMS 和 G1 之前的全部收集器,其工作的所有步骤都会产生 Stop The World。CMS 和 G1 分别使用增量更新和原始快照技术,实现了标记阶段的并发,但对标记后的清理仍未得到妥善解决。CMS 使用标记 - 清除算法,虽然可与用户线程并发执行,但会产生空间碎片,一旦碎片淤积过多就必然会 Stop The World。G1 虽然可以按更小粒度进行回收,但会出现短暂的停顿

本文要介绍的两款收集器:Shenandoah 和 ZGC,几乎整个工作过程都是并发的,只有初始标记、最终标记阶段有短暂的停顿,并且停顿时间基本固定,与堆的容量、对象数量无关。这两款目前仍处于实验状态的收集器,被官方命名为低延迟垃圾收集器

Shenandoah 收集器

Shenandoah 作为一款第一个不由 Oracle 开发的 HotSpot 收集器,被官方明确拒绝在 OracleJDK12 中支持 Shenandoah 收集器,因此 Shenandoah 收集器只在 OpenJDK 才会包含。Shenandoah 收集器能实现在任何堆内存大小下都把垃圾停顿时间限制在十毫秒以内,这意味着相比 CMS 和 G1,Shenandoah 不仅要进行并发的垃圾标记,还要并发低进行对象清理后的整理

Shenandoah 和 G1 有相似的堆内存布局,在初始标记、并发标记等许多阶段的处理思路都高度一致,甚至直接共享一部分代码。不同的是,虽然 Shenandoah 也是基于 Region 的堆内存布局,回收策略也和 G1 一致,但在管理堆内存方面,它与 G1 至少有三个明显的不同:

  • 支持并发的整理算法,G1 的回收阶段可以多线程并行,但不能与用户线程并发
  • Shenandoah 默认不使用分代收集
  • Shenandoah 摒弃了在 G1 中需耗费大量资源去维护的记忆集,改用连接矩阵的全局数据结构来记录跨 Region 的引用关系

SHenandoah 收集器的工作过程大致可分为以下九个阶段:

  • 初始标记

    首先标记与 GC Roots 直接关联的对象,需要 Stop The World

  • 并发标记

    遍历对象图,标记出全部可达对象,这个阶段与用户线程一起并发执行

  • 最终标记

    处理剩余的 SATB 扫描,并统计出回收价值最高的 Region,并构成一组回收集,该阶段会有短暂停顿

  • 并发清理

    这个阶段用于清理那些整个区域内连一个存活对象都没有找到的 Region

  • 并发回收

    把回收集里面的存活对象先复制一份到其他未被使用的 Region,并发执行的困难在于移动对象的同时,用户线程可能会对移动对象进行读写访问,移动对象是一次性行为,但移动之后整个内存中所有指向对象的引用还是旧对象的地址,还难在一瞬间全部改变过来。Shenandoah 将会通过读屏障和被称为 Brooks Pointers 的转发指针来解决

  • 初始引用更新

    并发回收复制对象结束后,还需把堆中所有指向旧对象的引用修正到复制后的新对象,这个操作称为引用更新。引用更新的初始化阶段实际上并没有做什么具体处理,只是为了建立一个线程集合点,确保所有并发回收阶段中进行的收集器线程都已经完成分配给它们的对象移动任务,会有短暂的停顿

  • 并发引用更新

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

  • 最终引用更新

    修正 GC Roots 中的引用,这个阶段是 Shenandoah 的最后一次停顿

  • 并发清理

    经过并发回收和引用更新后,整个回收集中所有的 Region 已无存活对象,最后一次并发清理回收这些 Region 的内存空间,供新对象分配使用

了解了 Shenandoah 收集器的工作过程,再来看一下 Shenandoah 用于支持并发整理的核心概念 —— 转发指针(Brooks Pointer)。此前,要做类似的并发操作,通常要在被移动对象原有的内存上设置保护指针,一旦用户程序访问到归属于旧对象的内存空间就会产生自陷中断,进入预设好的异常处理器,再由其中的代码逻辑把访问转发到复制后的新对象。这种方式虽然能实现对象移动和用户线程并发,但如果没有操作系统层面的直接支持,将导致用户态频繁切换到核心态,代价巨大

转发指针是在原有对象布局结构的最前面统一增加一个新的引用字段,在正常情况下,该引用指向对象自己。当对象拥有一份新的副本时,只需修改一处指针的值,即旧对象上转发指针的引用位置,使其指向新对象,便可将所有对该对象的访问转发到新的副本上。这样只要旧对象的内存仍然存在,虚拟机内存中所有通过旧地址访问的代码仍可继续使用,都会被转发到新对象继续工作

ZGC 收集器

ZGC 全称 Z Garbage Collector,是一款在 JDK11 新加入的具有实验性质的低延迟垃圾收集器,由 Oracle 公司研发。ZGC 与 Shenandoah 的目标高度相似,都希望在对吞吐量影响不大的前提下,实现任意堆内存大小下垃圾收集停顿时间限制在十毫秒以内,但两者的实现思路又有显著差异。ZGC 是一款基于 Region 内存布局的,不设分代的,使用读屏障、染色指针和内存多重映射等技术来实现可并发的标记 - 整理算法的,以低延迟为首要目标的一款垃圾收集器

首先从 ZGC 的内存布局说起,ZGC 的 Region 具有动态性,即动态创建和销毁,以及动态的区域容量大小。然后是 ZGC 的并发整理算法的实现,ZGC 采用的是染色指针技术(Colored Pointer)。从前,如果我们要在对象上存储一些额外信息,通常会在对象头中增加额外的存储字段,如哈希码、分代年龄、锁记录等。这种方式在有对象访问的场景下是很自然流程的,不会有问题,但如果对象存在被移动过的可能性,即不能保证能成功访问对象呢?又或者有一些根本就不会访问对象,但又希望得知对象的某些信息的场景呢?能不能从指针或者与对象内存无关的地方获取这些信息呢?

染色指针是一种直接将少量额外信息存储在指针上的技术,ZGC 甚至直接把标记阶段的标记信息记录在引用对象的指针上,因此,与其说可达性分析是遍历对象图来标记对象,不如说是遍历引用图来标记引用。使用染色指针有三大优势:

  • 染色指针可以使得某一 Region 的存活对象被移走之后,该 Region 能立即被释放和重用,而不必等待整个堆中所有指向该 Region 的引用都被修正才能清理
  • 染色指针可以直接记录对象引用的变动信息,减少内存屏障(尤其是写屏障)的使用
  • 染色指针可以作为一种可扩展的存储结构,用来记录更多与对象标记、重定位相关的数据

ZGC 的运行过程大致可划分为以下四个大的阶段,都是可以并发执行的,仅是两个阶段中间会存在短暂的停顿小阶段:

  • 并发标记(Concurrent Mark)

    遍历对象图做可达性分析,前后也要经历类似 G1、Shenandoah 的初始标记、最终标记的短暂停顿。与 G1、Shenandoah 不同的是,ZGC 的标记是在指针上而非对象,标记阶段会更新染色指针中的 Marked 0、Marked 1 标志位

  • 并发预备重分配(Concurrent Prepare for Relocate)

    根据特定的查询条件统计出本次收集过程要清理哪些 Region,将这些 Region 组成重分配集(Relocation Set)。ZGC 划分 Region 的目的并非像 G1 是为了做收益优先的增量回收,ZGC 每次回收都会扫描所有 Region,用范围更大的扫描成本换取维护记忆集的成本。ZGC 的标记过程是针对全堆的,ZGC 的重分配集只是决定里面的存活对象会被重新复制到其他的 Region 中,里面的 Region 会被释放,而不能说回收行为就只针对这个集合里面的 Region

  • 并发重分配(Concurrent Relocate)

    这个过程要把重分配集中的存活对象复制到新的 Region 上,并为重分配集中的每个 Region 维护一个转发表,记录从旧对象到新对象的转发关系。如果用户线程此时并发访问位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,并根据 Region 上的转发表记录将访问转发到新复制的对象上,同时更新该引用的值,使其指向新对象,这种行为称为指针的自愈(Self-Healing)能力

  • 并发重映射(Concurrent Remap)

    修正整个堆中指向重分配集中旧对象的所有引用,不过这并不是一项迫切完成的任务,因为即使是旧引用,它也是可以自愈的。因此,ZGC 把并发重映射阶段要做的工作,合并到下一次垃圾收集循环中的并发标记阶段去完成,反正都是要遍历所有对象图,这样还可以节省一次遍历对象图的开销。一旦所有指针被修正之后,原来记录新旧对象关系的转发表就可以释放掉了

JVM 低延迟垃圾收集器 Shenandoah 和 ZGC的更多相关文章

  1. JDK 11中的ZGC-一种可扩展的低延迟垃圾收集器

    # 背景正如我们所知道的在JDK 11中即将迎来ZGC(The Z Garbage Collector),这是一个处于实验阶段的,可扩展的低延迟垃圾回收器.本文整合了外网几篇介绍ZGC的文章和代码. ...

  2. 💕《给产品经理讲JVM》:垃圾收集器

    前言 在上篇中,我们把 JVM 中的垃圾收集算法有了一个大概的了解,又是一个阴雨连绵的周末,宅在家里的我们又开始了新一轮的学习: 产品大大:上周末我们说了垃圾收集算法,下面是不是要讲一下这些算法的应用 ...

  3. 【JVM】JVM中的垃圾收集器

    垃圾收集器组合 Serial+Serial Old Serial+CMS ParNew+CMS ParNew+Serial Old Paralle Scavenge + Serial Old Para ...

  4. Spark学习之路 (十四)SparkCore的调优之资源调优JVM的GC垃圾收集器

    一.概述 垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了. jvm 中,程序计数器.虚拟机栈.本 ...

  5. JVM笔记(二) 垃圾收集器(1)

    垃圾收集器 主要通过阅读<深入了解Java虚拟机>(周志明 著)和网络资源汇集而成,为本人学习JVM的笔记.同时,本文理论基于JDK 1.7版本,暂不考虑 1.8和1.9 的新特性,但可能 ...

  6. jvm系列 (二) ---垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 前言:本文基于<深入java虚拟机>再加上个人的理解以及其他相关资料,对内容进行整理浓缩总结.本文中的图来自网络,感谢图的作者.如果有不正确的地方,欢迎指出. 目 ...

  7. JVM学习记录-垃圾收集器

    先回顾一下上一篇介绍的JVM中常见几种垃圾收集算法: 标记-清除算法(Mark-Sweep). 复制算法(Copying). 标记整理算法(Mark-Compact). 分代收集算法(Generati ...

  8. 【JVM.2】垃圾收集器与内存分配策略

    垃圾收集器需要完成的3件事情: 哪些内存需要回收? 什么时候回收? 如何回收? 在前一节中介绍了java内存运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈3个区域随线程而生,随线程而灭:栈 ...

  9. JVM内存管理---垃圾收集器

    说起垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物.事实上,GC的历史远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态 ...

随机推荐

  1. C#(三)基础篇—方法,递归,条件分支,循环,三元操作符

    C# 本随笔为个人复习巩固知识用,多从书上总结与理解得来,如有错误麻烦指正 2020-12-03 1.方法 static void Main(string[] args) { float Sum(fl ...

  2. 适合 JS 新手学习的开源项目——在 GitHub 学编程

    作者:HelloGitHub-小鱼干 这里是 HelloGitHub 的<GitHub 上适合新手的开源项目>系列的最后一篇,系列文章: C++ 篇 Python 篇 Go 篇 Java ...

  3. day6(celery原理与组件)

    1.Celery介绍 1.1 celery应用举例 Celery 是一个 基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理,如果你的业务场景中需要用到异步任务,就可以考 ...

  4. .NET Core/.NET 5.0 析构函数依然有效?

    前言 最近看到小伙伴在.NET Core中用到了析构函数,不禁打一疑问,大部分情况下,即使在.NET Framework中都不会怎么用到析构函数,我想在.NET Core中是否还依然有效呢?随着时间推 ...

  5. 第3.11节 Python强大的字符串格式化新功能:format字符串格式化的格式控制

                                                第3.11节 format字符串格式化的格式控制 一.    引言 上节介绍了四种format进行字符串格式化的 ...

  6. 第8.2节 Python类的__init__方法深入剖析:构造方法案例详解

    前面一节介绍了构造方法定义的语法,并进行了语法解释说明,本节将通过案例来说明构造方法参数传递及返回值的情况. 一.    案例说明 本节定义一个汽车类,它有四个实例变量:wheelcount, pow ...

  7. 第11.8节 Python正则表达式的重复匹配模式及元字符“?”、 “*”、 “+”功能介绍

    符号"?".""."+"这三个元字符修饰符在Python中都表示重复匹配的模式,即要求匹配的字符串满足重复次数的要求,但具体重复次数要求不同 ...

  8. PyQt学习随笔:Model/View设计中支持视图中数据修改的方法及步骤

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 要支持视图中的数据可以修改,需要两个步骤: 1. 在视图中设置editTriggers属性支持在视图 ...

  9. ADF 第一篇:Azure Data Factory介绍

    Azure Data Factory(简写 ADF)是Azure的云ETL服务,简单的说,就是云上的SSIS.ADF是基于云的ETL,用于数据集成和数据转换,不需要代码,直接通过UI(code-fre ...

  10. 关于将Linux中默认的OpenJDK替换为JDK的方法

    首先下载需要的jdk安装包,后缀建议.tar.gz,本文中以jdk-8u212-linux-x64.tar.gz为例,地址就在oracle官网. 将安装包下载到linux环境后,使用命令tar -xz ...