【重难点】【JVM 03】CMS、G1、ZGC

【重难点】【JVM 03】CMS、G1、ZGC

一、CMS

1.介绍

在 JDK 1.5 时期,HotSpot 推出了一款在强交互应用中具有划时代意义的垃圾收集器:CMS(Concurrent - Mark - Sweep)收集器,这款收集器是 HotSpot 中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作

CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,停顿时间越短(低延迟)就越适合那些与用户交互的程序

CMS 的垃圾收集算法采用标记 - 清除,并且也会 “Stop - The - World”

不幸的是,CMS 作为老年代的收集器,无法与 JDK 1.4.0 中就已经存在的新生代收集器 Parallel Scavenge 配合工作,所以在 JDK 1.5 中使用 CMS 来收集老年代的时候,新生代只能选择 ParNew 或者 Serial 收集器

在 G1 出现之前,CMS 使用还是非常广泛的。一直到今天,仍然有很多系统使用 CMS GC

CMS 整个过程比之前的收集器要复杂,整个过程分为 4 个主要阶段,即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段

  • 初始标记(Initial - Mark)阶段:
    在这个阶段中,程序中所有的工作线程都将会因为 “Stop - The - World” 机制而出现短暂的暂停,这个阶段的主要任务仅仅是标记出 GC Roots 能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快
  • 并发标记(Concurrent - Mark)阶段:
    从 GC Roots 的直接关联对象开始,遍历整个对象图,这个过程耗时较长,但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
  • 重新标记(Remark)阶段:
    由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短
  • 并发清除(Concurrent - Sweep)阶段:
    此阶段清理删除掉标记阶段判断已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程并发执行的

尽管 CMS 收集器采用的是并发回收(非独占式),但是在其初始化标记和再次标记这两个阶段中仍然需要执行 “Stop - The - World” 机制暂停程序中的工作线程,不过暂停时间并不会太长,因此可以说明目前所有的垃圾收集器都做不到完全不需要 “Stop - The - World”,只是尽可能地缩短暂停时间

由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的

另外,由于在垃圾收集阶段,用户线程没有中断,所以在 CMS 回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS 收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在 CMS 工作过程中依然有足够的空间支持应用程序运行。要是 CMS 运行期间预留的内存无法满足程序需要,就会出现一次 “Concurrent Mode Failure” 失败,这时虚拟机将会启动后备预案,即临时启用 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了

CMS 收集器的垃圾收集算法采用的是标记 - 清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的对象所占用的内存空间极有可能是不连续的一些内存块,不可避免地会产生一些内存碎片。那么 CMS 在为新对象分配内存空间时,将无法使用指针碰撞(Bump The Poibter)技术,而只能够选择空闲列表(Free List)执行内存分配

但是即使是这样也不会采用标记 - 压缩算法,因为当并发清除的时候,用 Compact 整理内存的话,原来的用户线程使用的内存就无法使用了。要保证用户线程能继续执行的前提是它运行的资源不受影响。标记 - 压缩适合 STW 的场景下使用

2.优点

  • 并发收集
  • 低延迟

3.缺点

  • 会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发 Full GC
  • CMS 收集器会对 CPU 资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低
  • CMS 收集器无法处理浮动垃圾。可能出现 “Concurrent Mode Failure” 失败而导致另一次 FUll GC 的产生。在并发标记阶段,由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS 将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行 GC 时释放这些之前未被回收的内存空间

二、G1

1.介绍

诞生背景

及你拿来,应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有 GC 就不能保证应用程序正常运行,而经常造成 STW 的 GC 又跟不上实际的需求,所以才会不断地尝试对 GC 进行优化.G1(Gabage First)垃圾收集器是 Java 7 update 4 之后引入的一个新的垃圾收集器,是当今收集器技术发展地最前沿成果之一

与此同时,为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time),同时兼顾良好的吞吐量。官方给 G1 设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担当起 “全功能收集器” 的重任与期望(同时负责新生代和老年代)

名字由来

G1 是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续)。使用不同的 Region 来表示 Eden、幸存者 0 区、幸存者 1 区、老年代等

G1 GC 有计划地避免在整个 Java 堆中进行全区域的垃圾收集。G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region

由于这种方式的侧重点在于回收垃圾最大量的区域,所以我们给 G1 一个名字:垃圾优先(Garbage First)

2.优势

并行与并发

  • 并行性:G1 在回收期间,可以有多个 GC 线程同时工作,有效利用多个 GC 线程同时工作,有效利用多和计算能力。此时用户线程 STW
  • 并发性:G1 拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行。因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况

分代收集

  • 从分代上看,G1 依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有 Eden 区和 Survivor 区。但从堆的结构上看,它不要求整个 Eden 区、新生代或者老年代都是连续的,也不再坚持固定大小和固定数量
  • 将堆空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代
  • 和之前的各类回收期不同,它同时兼顾新生代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代

空间整合

  • CMS:“标记 - 清除” 算法、内存碎片、若干次 GC 后进行一次碎片整理
  • G1:将内存划分为一个个的 Region。内存的回收是以 Region作为基本单位的。Region 之间是复制算法,但整体上实际可看作是标记 - 压缩(Mark - Compact)算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前出发下一次 GC。尤其是当 Java 堆非常大的时候,G1 的优势更加明显

可预测的停顿时间模型

即软实时,这是 G1 相对于 CMS 的另一大优势,G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒

  • 由于分区的原因,G1 可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制
  • G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。保证了 G1 收集器在有限的时间内可以获取尽可能高的收集效率

相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多
即便如此,G1 相较于 CMS 还不具备全方位、压倒性优势。比如在用户程序运行过程中,G1 无论是为了垃圾收集产生的内存占用(Footprint),还是程序运行时的额外执行负载(Overload)都要比 CMS 高

从经验上来说,在小内存应用上 CMS 的表现大概率会优于 G1,而 G1 在大内存应用上则更具优势。平衡点在 6GB - 8GB 之间

简化性能调优

G1 的设计原则就是简化 JVM 性能调优,开发人员只需要简单的三步即可完成调优:

  1. 开启 G1 垃圾收集器
  2. 设置堆的最大内存
  3. 设置最大的停顿时间

G1 中提供了三种垃圾回收模式:YoungGC、Mixed GC 和 Full GC,在不同的条件下被触发

3.应用场景

  • 面向服务端应用,针对具有大内存、多处理器的机器(在普通大小的堆里表现平平)
  • 主要为需要低 GC 延迟,并具有大堆的应用程序提供解决方案
  • 用来替换 JDK 1.5 中的 CMS 收集器,在下面的情况,使用 G1 可能比 CMS 好
    • 超过 50% 的 Java 堆被活动数据占用
    • 对用分配频率或年代提升频率变化很大
    • GC 停顿时间过长(大于 0.5s)
  • HotSpot 垃圾收集器里,除了 G1 以外,其他的垃圾收集器都是使用内置的专门的 JVM 线程执行 GC 的多线程操作,而 G1 GC 可以采用应用程序线程承担后台运行的 GC 工作,即当 JVM 的 GC 线程处理速度慢时,系统会调用应用程序线程帮助加速垃圾回收过程

4.Region 介绍

使用 G1 收集器时,它将整个 Java 堆划分成约 2048 个大小相同的独立 Region 块,每个 Region 块大小根据堆空间的实际大小而定,整体被控制在 1MB 到 32MB 之间,且为 2 的 N 次幂,即 1MB、2MB、4MB、8MB、16MB、32MB。可以通过参数设定。所有的 Region 大小相同,且在 JVM 生命周期内不会被改变

虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分 Region(不需要连续)的集合。通过 Region 的动态分配方式实现逻辑上的连续

在这里插入图片描述

  • 一个 Region 有可能属于 Eden、Survivor 或者 Old/Tenured 内存区域。但是一个 Region 同一时间只可能属于一个角色
  • G1 垃圾收集器还增加了一种新的内存区域,叫做 Humongous 内存区域,如果一个对象超过 1.5 个 Region,就会放到 Humongous。之所以这么设计是因为,之前对于堆中的大对象,默认直接会被分配到老年代,但是如果它是一个短期存在的大对象,就会对垃圾收集器造成负面影响;而 G1 划分了一个 Humongous 区,专门存放大对象。如果一个 H 区装不下一个大对象,那么 G1 就会寻找连续的 H 区来存储。为了能找到连续的 H 区,有时候不得不启动 Full GC。G1 的大多数行为都把 H 区作为老年代的一部分来看待

5.G1 垃圾回收过程

G1 GC 的垃圾回收过程主要包括如下三个环节:

  • 新生代 GC(Young GC)
  • 老年代并发标记过程(Concurrent Marking)
  • 混合回收
  • (如果需要,单线程、独占式、高强度的 Full GC 还是继续存在的。它针对 GC 的评估(参数预设)失败提供了一种失败保护机制,即强力回收)

在这里插入图片描述

应用程序分配内存,当新生代的 Eden 区用尽时开始新生代的回收。G1 的新生代收集阶段是一个并行的独占式收集器。在年轻代回收期,G1 GC 暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到 Survivor 区间或者 Old 区间,也有可能是两种区间都会涉及

当堆内存使用达到一定值(默认 45%)时,开始老年代并发标记过程

标记完成马上开始混合回收。在混合回收期,G1 GC 从 Old 区间移动存活对象到空闲区间,然后空闲区间就变成了 Old 区间。老年代的 G1 回收器和其他 GC 不同,G1 的老年代回收器不需要整个老年代都被回收,一次只需要扫描/回收一小部分老年代的 Region 就可以了。同时,这个老年代的 Region 是和年轻代一起被回收的,而不是专门回收其中一种

举个例子:一个 Web 服务器,Java 进程最大堆内存为 4G,每分钟响应 1500 个请求,没 45 秒会新分配大约 2G 的内存。G1 会每 45 秒进行一次年轻代回收,每 31 个小时整个堆的使用率会达到 45%,会开始老年代并发标记过程,标记完成后开始四到五次的混合回收

1、年轻代 GC

JVM 启动时,G1 先准备好 Eden 区,程序在运行过程中不断创建对象到 Eden 区,当 Eden 空间耗尽时,G1 会启动一次年轻代垃圾回收过程

年轻代 GC 时,首先 G1 停止应用程序的执行(Stop - The -World),G1 创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集合包含年轻代 Eden 区和 Survivor 区所有的内存分段

在这里插入图片描述

图片的上半部分表示回收前,下半部分表示回收后

详细过程

  1. 扫描根
    根是指 static 变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同记忆集(下面有介绍)记录的外部引用作为扫描存活对象的入口
  2. 更新记忆集
    处理脏卡表(下面有介绍)中的 card,更新记忆表。此阶段完成后,记忆集可以准确地反映老年代对所在的内存分段中对象的引用
  3. 处理记忆集
    识别被老年代对象指向的 Eden 中的对象,这些被指向的 Eden 中的对象被认为是存活的对象
  4. 复制对象
    此阶段,对象树被遍历,Eden 区内存段中存活的对象会被复制到 Survivor 区中空的内存分段,Suvivor 区内存段中存活的对象如果年龄未达到阈值,年龄会加 1,达到阈值会被复制到 Old 区中空的内存分段。如果 Survivor 空间不够,Eden 空间的部分数据会直接晋升到老年代空间
  5. 处理引用
    处理 Sofr、Weak、Phanto、Final、JNI Weak 等引用。最终 Eden 空间的数据为空,GC 停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片

记忆集 Remembered Set

进行垃圾回收时,我们会遇到如下问题

  • 一个 Region 不可能是孤立的,一个 Region 中的对象可能被其他任意 Region 中的对象引用,判断对象存活时,应该怎么确保一个对象没被其他任何对象引用?

解决方案

在这里插入图片描述

  • 无论 G1 还是其他分代收集器,JVM 都是使用 Remembered Set 来避免全局扫描
  • 每个 Region 都有一个对应的 Remembered Set
  • 每次 Reference 类型数据写操作时都会产生一个 Write Barrier 暂时中断操作
  • 然后检查将要写入的引用指向的对象是否和该 Reference 类型数据在不同的 Region(其他收集器是检查老年代对象是否引用了新生代对象)
  • 如果不同,通过 CardTable 把相关引用信息记录到引用指向对象的所在 Region 对应的 Remembered Set 中
  • 当进行垃圾收集时,在 GC 根结点的枚举范围加入 Remembered Set。这样,就可以保证不进行全局扫描,也不会有遗漏

脏卡表 Dirty Card Queue

对于应用程序的引用赋值语句 object.field = object,JVM 会在之前和之后执行特殊的操作以在脏卡表中入队一个保存了对象引用信息的 card。在年轻代回收的时候,G1 会对脏卡表中所有的 card 进行处理,以更新记忆集,保证 Rset 实时准确地反映引用关系

那为什么不在引用赋值语句处直接更新记忆集呢?

这是为了性能的需要,记忆集的处理需要线程同步,开小会很大,使用队列性能会好很多

2、并发标记过程

  1. 初始标记阶段
    标记从根结点直接可达的对象。这个阶段是 STW 的,并且会触发一次年轻代 GC
  2. 根区域扫描(Root Region Scanning)
    G1 GC 扫描 Survivor 区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在年轻代 GC 之前完成
  3. 并发标记(Concurrent Marking)
    在整个堆中进行并发标记(和应用程序并发执行),此过程可能被年轻代 GC 中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)
  4. 再次标记(Remark)
    由于应用程序持续进行,需要修正上一次的标记结果。是 STW 的。G1 中采用了比 CMS 更快的初始化快照算法 Snapshot -At - The - Begining(SATB)
  5. 独占清理(cleanup,STW)
    计算各个区域的存活对象和 GC 回收比例,并进行排序,识别可以混合回收的区域,为下阶段做铺垫
  6. 并发清理阶段
    识别并清理完全空闲的区域

3、混合回收

当越来越多的对象晋升到老年代区域时,为了避免堆内存被耗尽,虚拟机触发一个混合的垃圾收集器,即 Mixed GC。除了回收整个新生代区域,还会回收一部分老年代区域。对于老年代区域可以选择性地回收,从而对垃圾回收的耗时进行控制。此外,注意区分 Mixed GC 和 Full GC
在这里插入图片描述
并发标记结束以后,老年代中百分百为垃圾的内存分段已经被回收了,部分为垃圾的内存分段中的垃圾比例也被计算出来了。默认情况下,这些老年代的内存分段会分 8 次进行回收,次数可以通过参数设置

混合回收的算法和年轻代回收的算法完全一样,只是回收集(Collection Set)多了老年代的内存分段

由于老年代中的内存分段默认分 8 次回收,G1 会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收,可以通过参数设置阈值决定,默认为 65%,意思是垃圾占内存分段比例要达到 65% 才回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间

混合回收并不一定要进行 8 次。有一个参数:-XX:G1HeapWastePercent,默认值为 10%,意思是允许整个堆内存中有 10% 的空间被浪费,意味着如果发现可回收的垃圾占堆内存的比例低于 10%,则停止混合回收。因为付出的 GC 时间得不到匹配的回报,即回收的垃圾太少

G1 Full GC

G1 的初衷是要避免 Full GC 的出现,但是如果上述方式不能正常工作,G1 会停止应用程序的执行(STW),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长

导致 G1 Full GC 的原因有两个:

  1. 回收的时候 Survivor 区没有足够的 to 空间来存放晋升的对象
  2. 并发处理过程完成之前空间耗尽

三、ZGC

1.介绍

《深入理解 Java 虚拟机》一书中这样定义 ZGC:ZGC 收集器是一款基于 Region 内存布局的,(暂时)不设分代,使用了读屏障、染色指针和多重映射等技术实现可并发的标记 - 压缩算法、以低延迟为首要目标的一款垃圾收集器

ZGC 的工作过程可以分为 4 个阶段:并发标记 - 并发预备重分配 - 并发重分配 - 并发重映射

ZGC 几乎在所有地方并发执行,只有初始标记需要 STW,并且这部分实际耗时也是非常少的

2.优势

高吞吐量、低延迟

ZGC 支持 “NUMA-aware” 的内存分配。NUMA(Non-Uniform Memory Access,非同一内存访问的架构)是一种多处理器或多核处理器计算机所设计的内存架构

现在多 CPU 插槽的服务器都是 NUMA 架构,比如两颗 CPU 插槽(24 核),64 GB 内存的服务器,每颗 CPU 都有一个从属于自己的 32 GB 本地内存,CPU 访问本地内存肯定会比访问另一颗 CPU 的内存快,当然也会比不使用 NUMA 架构,而是两颗 CPU 访问同一块 64 GB 的共享内存要快

ZGC 默认支持 NUMA 架构,在创建对象时,根据当前线程在哪个 CPU 执行,优先在靠近这个 CPU 的内存进行分配,这样可以显著地提高性能,在 SPEC JBB 2005 基准测试里获得 40% 地提升

3.缺点

浮动垃圾

当 ZGC 准备要对一个很大地堆做一次完整地并发收集时,假设其全过程要持续十分钟以上,由于应用的对象分配速率很高,将会创建大量的新对象,这就导致这些新对象很难进入当次收集地标记范围,通常就只能全部作为存活对象来看待,尽管其中绝大部分对象都是朝生夕灭,因此产生了大量的浮动垃圾

目前唯一的办法就是尽可能地区增加对容量大小,获取更多喘息的时间。但若要从根本上解决,还是需要引入分代收集,让新生对象都在一个专门的区域中创建,然后针对这个区域进行更频繁、更快的收集

4.ZGC 的布局

与 Shenandoah、G1 一样,ZGC 也采取基于 Region 的堆内存布局,但与他们不同的是,ZGC 的 Region 具有动态性(动态地创建和销毁,以及动态的区域容量大小)

ZGC 的 Region 可以分为三类:

  • 小型 Region:容量固定为 2 MB,用于放置小于 256 KB 的小对象
  • 中型 Region:容量固定为 32 MB,用于防止大于等于 256 KB 但小于 4MB 的对象
  • 大型 Region:容量不固定,可以动态变化,但必须是 2 MB 的整数倍,用于存放 4 MB 以上的大对象,并且每个大型 Region 只会存放一个对象

5.ZGC 的特性

Concurrent(并发执行)

ZGC 只有短暂的 STW,大部分的过程都是和应用线程并发执行,比如最耗时的并发标记和并发移动过程

Region - based(内存基于 Region)

ZGC 中没有新生代和老年代的概念,只有一块一块的内存区域,但和 G1 不一样的是,Region 的大小更加灵活和动态,不会像 G1 那样在一开始就被划分为固定大小

Compacting(压缩)

每次进行 GC 时,都会对 Region 进行压缩,所以完全避免了 CMS 算法中的碎片化问题

NUMA-aware(NUMA 架构)

ZGC 对 NUMA 的支持是小分区分配时会优先从本地内存分配,如果本地内存不足则从远程内存分配。对于中、大分区的话就交由操作系统决定

上述做法的原因是生成的绝大部分都是小分区对象,因此优先本地分配速度较快,而且也不易造成内存不平衡的情况。而中、大分区对象较大,如果都从本地分配则可能会导致内存不平衡

Using colored pointers(着色指针)

HotSpot 的垃圾收集器,有几种不同的标记实现方案

  • 把标记直接记录在对象头上(Serial 收集器)
  • 把标记记录在与对象相互独立的数据结构上(G1、Shenandoah 使用了一种相当于堆内存的 1/64 大小的,称为 BitMap 的结构来记录标记信息)
  • ZGC 染色指针直接把标记信息记在引用对象的指针上

ZGC 染色指针的优势:

  • 染色指针可以使得一旦某个 Region 的存活对象被移走之后,这个 Region 立刻就能被释放和重用掉,而不必等待整个堆中所有指令向该 Region 的引用都被修正后才能清理
  • 染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,设置内存屏障,尤其是写屏障,目的通常是为了记录对象引用的变动情况,如果将这些信息直接维护在指针中,显然可以省去一些专门的记录操作
  • 染色指针可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能

Using load barriers(读屏障)

在 CMS 和 G1 中都用到了写屏障,而 ZGC 用到了读屏障

写屏障是在对象引用赋值的时候的 AOP,而读屏障是在读取引用时的 AOP

因为在标记和移动过程中,GC 线程和应用线程是并发执行的,所以存在这种情况:对象 A 内部的引用所指的对象 B 在标记或者移动状态,为了保证应用线程拿到的 B 对象是对的,那么在读取 B 的指针时会经过一个 “load barriers”,这个读屏障可以保证在执行 GC 时,数据读取的正确性

6.ZGC 的运作过程

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

在这里插入图片描述

  • 并发标记(Concurrent Mark):
    与 G1、Shenandoah 一样,并发标记是遍历对象图做可达性分析的阶段,前后也要经过类似于 G1、Shenandoah 的初始标记、最终标记的短暂停顿,而且这些停顿阶段所做的事情在目标上也是相类似的
  • 并发预备重分配(Concurrent Prepare for Relocate):
    这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些 Region,将这些 Region 组成重分配集(Relocation Set)
  • 并发重分配(Concurrent Relocate):
    重分配是 ZGC 执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的 Region 上,并为重分配集中的每个 Region 维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系
  • 并发重映射(Concurrent Remap):
    重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,ZGC 并发映射并不是以一个必须要 “迫切” 去完成的任务。ZGC 很巧妙地把并发重映射阶段要做的工作,合并到下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样节省了一次遍历的开销
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

313YPHU3

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值