JVM工作原理与实战(四十):ZGC原理

简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了ZGC、ZGC核心技术、ZGC的内存划分、ZGC的执行流程、分代ZGC的设计等内容。

一、ZGC介绍

ZGC(Z Garbage Collector)是一种高效且可扩展的低延迟垃圾回收器。在垃圾回收过程中,ZGC通过优化算法和硬件支持,将Stop-The-World(STW)时间控制在一毫秒以内,使其成为追求低延迟应用的理想选择。此外,ZGC支持灵活的堆大小配置,从几百兆到16TB的堆大小均可轻松应对,且堆大小对STW时间的影响微乎其微。

相比之下,G1垃圾回收器在STW时间方面有所不同。G1的STW时间主要来源于其转移阶段,具体包括以下几个步骤:

  1. 初始标记(Initial Mark):STW阶段,采用三色标记法快速标记从GC Root直接可达的对象。此阶段的STW时间通常非常短暂。
  2. 并发标记(Concurrent Mark):并发执行阶段,对存活对象进行标记。这个阶段允许用户线程和GC线程同时工作,从而减少了STW时间。
  3. 最终标记(Final Mark):STW阶段,处理SATB(Snapshot-At-The-Beginning)相关的对象标记。此阶段的STW时间同样较短。
  4. 清理(Cleanup):STW阶段,如果某个区域中没有存活对象,则直接清理该区域。此阶段的STW时间也较短。
  5. 转移(Copy):将存活对象复制到其他区域。这是G1垃圾回收器中STW时间较长的阶段,因为它涉及对象的复制和引用更新。

G1转移时需要停顿的主要原因是确保对象引用的正确性。在转移过程中,如果允许用户线程和GC线程同时工作,可能会出现以下问题:假设对象A引用了对象C,在转移过程中,C对象被复制到新的区域。如果此时用户线程修改了A对象的某个属性(如A.c.count = 2),但实际上这个修改是针对转移前的C对象。当转移完成后,A对象的引用被更新为指向新的C对象,但此时获取A.c.count的值仍然是1,因为修改是在转移前进行的。这种不一致性会导致数据错误。

image.gif

为了解决这个问题,G1垃圾回收器在转移过程中需要暂停用户线程,确保转移操作的原子性和正确性。然而,ZGC和Shenandoah等新型垃圾回收器解决了这一问题,使得转移过程也能够并发执行,从而进一步提高了性能。

二、ZGC核心技术

1.读屏障(Load Barrier)

在ZGC中,采用了读屏障(Load Barrier)技术来处理对象引用的获取。当尝试获取一个对象引用时,读屏障会检查该引用是否指向了转移后的对象。如果不是,用户线程会将引用更新为指向转移后的对象。这种机制确保了用户线程总是看到最新、最正确的对象状态,从而避免了上述的不一致性问题。这种设计使得ZGC在保持低延迟的同时,也实现了高效的垃圾回收。

image.gif

2.着色指针(Colored Pointers)

着色指针(Colored Pointers)是一种内存管理优化技术,用于在64位虚拟机中更有效地利用内存地址空间。在64位系统中,指针通常占用8个字节,这足以表示接近无限的内存空间。然而,大多数应用程序使用的内存远小于这个范围,因此指针的高位通常是未使用的。着色指针技术正是利用了这些未使用的位来存储关于对象状态的信息。

image.gif

着色指针的设计将原本8字节的地址指针巧妙地拆分成了三个部分:

  • 低44位地址:用于直接表示对象的内存地址,这个范围足够覆盖大多数应用程序所使用的内存空间,最多可以表示16TB的内存。
  • 中间4位颜色位:这四位用于存储关于对象状态的信息。每一位只能存储0或1,并且同一时间只有一位可以是1。不同的位代表不同的状态:
  • 终结位:当设置时,表示该对象只能通过终结器(Finalizer)进行访问。
  • 重映射位(Remap):表示在垃圾收集转移过程后,对象的引用关系已经更新。
  • Marked0和Marked1:用于标记对象是否可达,是垃圾收集过程中的重要标识。
  • 高16位未使用:这些位在当前实现中未被使用,但为未来扩展提供了空间。

image.gif

应用程序通常使用8个字节的地址来访问对象,但在ZGC中,仅使用其中的44位来表示对象的内存地址。这看似是一个缩减,但实际上并不会引发问题。这是因为应用程序所使用的对象地址主要是虚拟内存地址,这些地址在最终执行时会被操作系统映射到物理内存上。ZGC通过调整操作系统的内存管理逻辑,确保了即使着色指针中的颜色位发生变化,指针依然能够准确地指向目标对象。这样的设计让着色指针技术在保留原有寻址功能的同时,还额外提供了关于对象状态的信息,从而显著提高了垃圾收集的性能和效率。

image.gif

3.ZGC核心技术总结

1. 着色指针(Colored Pointers)

  • 指针拆分:ZGC的着色指针技术将传统的8字节地址指针创新性地拆分为三部分,不仅保留了指向对象地址的功能,还额外承载了对象当前所属状态的信息。
  • 状态标识:通过指针中的颜色位,ZGC能够迅速识别对象的存活状态,从而优化垃圾回收过程。
  • 系统兼容性:着色指针技术目前不支持32位系统,并且不兼容指针压缩技术。

2. 读屏障(Load Barrier)

  • 状态检查:在用户线程访问对象引用时,读屏障机制会介入检查该对象的当前状态。如果对象的所属状态与当前GC阶段所期望的颜色状态不一致,读屏障会触发相应的处理逻辑。
  • 用户线程参与:在这种情况下,读屏障会指导用户线程完成本阶段所需的转移或更新工作,确保对象引用的正确性。
  • 性能影响:虽然读屏障机制确保了垃圾回收的正确性,但它也会带来一定的性能开销。根据实际应用场景和负载情况,性能损失通常在5%~10%之间。

三、ZGC的内存划分

ZGC(Z Garbage Collector)的内存布局策略是其高效和低延迟特性的关键之一。类似于G1垃圾回收器,ZGC也将堆内存划分为多个独立的区域,这些区域被称为Zpage。然而,与G1不同的是,ZGC对Zpage的划分更加精细,旨在实现更精确的内存管理和控制,从而进一步减少垃圾回收过程中的停顿时间。ZGC的Zpage可以分为三类,分别是小区域、中区域和大区域,它们各自具有不同的容量和用途:

  • 小区域(Small Zpage):每个小区域的大小为2MB,专门用于存放小型对象,即大小不超过256KB的对象。这种划分方式有助于减少内存碎片,并提高内存利用率。
  • 中区域(Medium Zpage):每个中区域的大小为32MB,用于存放中等大小的对象,即大小在256KB到4MB之间的对象。中区域的引入进一步扩展了ZGC的适用范围,使其能够处理更大规模的内存需求。
  • 大区域(Large Zpage):大区域的大小则根据实际需求动态分配,每个大区域只保存一个大于4MB的大型对象。这种设计允许ZGC在必要时为大型对象提供足够的连续内存空间。

通过这种精细的内存划分策略,ZGC能够在垃圾回收过程中实现更精确的内存管理,从而有效控制停顿时间,提高整体性能。

四、ZGC的执行流程

image.gif

  • 初始标记阶段:在此阶段,ZGC会标记所有由GC Roots直接引用的对象作为存活对象。由于这些存活对象的数量通常不多,因此此阶段的停顿时间非常短暂,对应用程序的影响几乎可以忽略不计。

image.gif

  • 并发标记阶段:在这一阶段,ZGC会遍历堆中的所有对象,并使用读屏障和写屏障来检查每个对象是否可达。用户线程在此过程中会协助进行标记工作,如果它们发现某个对象尚未完成标记,它们会主动参与标记过程,确保没有遗漏。

image.gif

  • 并发处理阶段:在这个阶段,ZGC会选择需要转移的Zpage,并创建转移表。这个转移表用于记录每个对象在转移前后的地址映射关系,确保在后续的转移过程中能够准确地找到对象的新位置。

image.gif

  • 转移开始阶段:ZGC首先会转移与GC Root直接关联的对象。对于不需要转移的对象,它们的remapped值会被设置为1,以避免在后续过程中重复进行转移判断。完成转移后,ZGC会将新旧对象的地址对记录到转移映射表中。

image.gif

  • 并发转移阶段:在并发转移阶段,ZGC会将剩余的对象转移到新的ZPage中,并在转移后将新旧对象的地址对记录到转移映射表中。转移完成后,原来的Zpage就可以被安全地清空了,但转移表需要保留下来以供后续使用。此时,如果用户线程尝试访问一个对象的引用(例如,对象4引用对象5),它会通过读屏障来检查这个引用。如果发现引用的对象已经完成了转移(例如,对象5已经转移到了新的位置),它会更新这个引用,使其指向对象5的新位置,并将remap标记为1,表示已经完成了重新映射。并发转移阶段结束后,这一轮的垃圾回收就完成了。然而,需要注意的是,此时并没有完成所有指针的重映射工作,这个工作会被推迟到下一阶段,与下一轮的标记阶段一起完成(因为这两个阶段都需要遍历整个对象图)。

image.gif

第二次垃圾回收

  • 初始标记阶段:在第二次垃圾回收的初始标记阶段,ZGC会再次沿着GC Root标记对象。这是为了确保在上一轮垃圾回收后新增的存活对象也能被正确标记。

image.gif

  • 并发标记阶段:在并发标记阶段,ZGC会检查每个对象的Marked值。如果Marked为1,表示上一轮的重映射还没有完成,ZGC会首先完成重映射工作,从转移表中找到老对象转移后的新位置,然后再进行标记。如果对象的Remap值为1,则只需要进行标记操作。

image.gif

  • 并发处理阶段:在并发处理阶段,ZGC会删除转移映射表并释放相应的内存空间。这是因为经过上一轮的转移和标记后,这些映射信息已经不再需要了。

image.gif

并发转移阶段的并发问题

在ZGC的并发转移阶段,处理并发问题的机制是高度精细化的。这个阶段涉及用户线程和GC线程之间的协作,以确保在对象转移过程中的线程安全和效率。当用户线程尝试转移一个对象时,它会在转移映射表中记录该对象的新地址。然而,如果这个操作与GC线程的计划发生冲突,即GC线程也打算移动这个对象,那么GC线程会采取主动让步的策略。

GC线程在检测到潜在的冲突时,会放弃自己的写入操作,因为它知道用户线程已经先一步进行了转移工作。这种放弃不是简单的停止操作,而是GC线程的一种智能决策,它基于ZGC的并发转移策略和对系统状态的理解。通过这种方式,ZGC确保了并发转移阶段的正确性和效率,避免了线程间的竞争和潜在的冲突。

这种协同工作的方式不仅减少了线程间的交互成本,还提高了系统的整体性能。它充分利用了多核处理器的并行处理能力,使得用户线程和GC线程能够高效协同工作,共同完成对象的转移任务。因此,在ZGC的并发转移阶段,通过用户线程和GC线程之间的智能协作,确保了垃圾收集过程的顺利进行和系统的高效运行。

image.gif

五、分代ZGC的设计

在JDK 21及之后的版本中,ZGC引入了分代设计,明确区分了年轻代和老年代。这种设计的主要目的是优化垃圾回收的性能和效率。通过将大部分对象的生命周期限制在年轻代,ZGC能够减少对老年代的扫描次数,从而降低了垃圾回收的开销。同时,由于年轻代和老年代的垃圾回收可以并行执行,进一步提高了系统的吞吐量和响应速度。

在分代设计的基础上,ZGC对着色指针进行了相应的调整。原本用于保存8字节地址的指针被拆分成了三个部分,以更好地适应分代垃圾回收的需求:

  • 46位对象地址:这是指针的主要部分,用于直接表示对象的内存地址。46位的宽度可以支持最多64TB的地址空间,为大型应用提供了充足的内存管理空间。
  • 12位颜色位:这些位用于标识对象的颜色状态,是ZGC垃圾回收过程中的关键信息。颜色位可以帮助ZGC快速判断对象是否存活,从而进行有效的垃圾回收。
  • 未使用的4位和2位:指针的最低4位和最高2位在当前设计中并未被使用,为未来的扩展和优化留下了空间。这种设计灵活性使得ZGC能够适应不断变化的应用需求和硬件环境。

image.gif

通过分代设计和着色指针的调整,ZGC在保持高效垃圾回收的同时,进一步提升了系统的性能和可扩展性。这些改进措施使得ZGC成为大型应用和高性能场景下的理想选择。


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了ZGC、ZGC核心技术、ZGC的内存划分、ZGC的执行流程、分代ZGC的设计等内容,希望对大家有所帮助。

相关文章
|
4月前
|
存储 算法 Oracle
极致八股文之JVM垃圾回收器G1&ZGC详解
本文作者分享了一些垃圾回收器的执行过程,希望给大家参考。
|
8天前
|
NoSQL Java Redis
秒杀抢购场景下实战JVM级别锁与分布式锁
在电商系统中,秒杀抢购活动是一种常见的营销手段。它通过设定极低的价格和有限的商品数量,吸引大量用户在特定时间点抢购,从而迅速增加销量、提升品牌曝光度和用户活跃度。然而,这种活动也对系统的性能和稳定性提出了极高的要求。特别是在秒杀开始的瞬间,系统需要处理海量的并发请求,同时确保数据的准确性和一致性。 为了解决这些问题,系统开发者们引入了锁机制。锁机制是一种用于控制对共享资源的并发访问的技术,它能够确保在同一时间只有一个进程或线程能够操作某个资源,从而避免数据不一致或冲突。在秒杀抢购场景下,锁机制显得尤为重要,它能够保证商品库存的扣减操作是原子性的,避免出现超卖或数据不一致的情况。
40 10
|
2月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
24天前
|
存储 IDE Java
实战优化公司线上系统JVM:从基础到高级
【11月更文挑战第28天】Java虚拟机(JVM)是Java语言的核心组件,它使得Java程序能够实现“一次编写,到处运行”的跨平台特性。在现代应用程序中,JVM的性能和稳定性直接影响到系统的整体表现。本文将深入探讨JVM的基础知识、基本特点、定义、发展历史、主要概念、调试工具、内存管理、垃圾回收、性能调优等方面,并提供一个实际的问题demo,使用IntelliJ IDEA工具进行调试演示。
24 0
|
2月前
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
60 2
|
2月前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
5月前
|
存储 算法 安全
(八)JVM成神路之GC分区篇:G1、ZGC、ShenandoahGC高性能收集器深入剖析
在《GC分代篇》中,我们曾对JVM中的分代GC收集器进行了全面阐述,而在本章中重点则是对JDK后续新版本中研发推出的高性能收集器进行深入剖析。
197 12
|
5月前
|
运维 监控 Java
(十)JVM成神路之线上故障排查、性能监控工具分析及各线上问题排错实战
经过前述九章的JVM知识学习后,咱们对于JVM的整体知识体系已经有了全面的认知。但前面的章节中,更多的是停留在理论上进行阐述,而本章节中则更多的会分析JVM的实战操作。
131 1
|
5月前
|
缓存 监控 Java
Java虚拟机(JVM)性能调优实战指南
在追求软件开发卓越的征途中,Java虚拟机(JVM)性能调优是一个不可或缺的环节。本文将通过具体的数据和案例,深入探讨JVM性能调优的理论基础与实践技巧,旨在为广大Java开发者提供一套系统化的性能优化方案。文章首先剖析了JVM内存管理机制的工作原理,然后通过对比分析不同垃圾收集器的适用场景及性能表现,为读者揭示了选择合适垃圾回收策略的数据支持。接下来,结合线程管理和JIT编译优化等高级话题,文章详细阐述了如何利用现代JVM提供的丰富工具进行问题诊断和性能监控。最后,通过实际案例分析,展示了性能调优过程中可能遇到的挑战及应对策略,确保读者能够将理论运用于实践,有效提升Java应用的性能。 【
213 10