ZGC(即 Z 垃圾收集器)是 Java 垃圾GC领域中一个相对较新的选项。
垃圾收集器 (GC) 清理未使用的对象,为新对象释放内存。但仅仅跟踪可用空间是不够的。随着时间的推移,随着对象的创建和删除,内存会变得碎片化(分散)。为了防止这种情况,JVM 还可能压缩内存,重新排列内容以创建更大的连续可用空间块以供将来分配。
虽然细节可能很技术性,但垃圾收集 (GC) 可以归结为三个关键点:查找未使用的对象、释放其内存以及优化空闲内存。
不同的 GC 算法以独特的方式处理这些任务,尤其是在组织内存方面。有些人等到绝对必要时才进行重组,而另一些人则以更大的块或一次移动一小部分来处理它。这些不同的方法使得某些 GC 算法在特定情况下更快或更慢。
GC 的一个棘手问题是有时它需要在内存中移动对象。这可能是一个问题,因为如果应用程序线程在移动对象的同时尝试使用该对象,则可能会出现问题。为了防止这种情况,GC 在重新定位对象时会短暂暂停所有应用程序线程。
这称为“stop-the-world”暂停。
大多数垃圾收集器以类似的方式组织内存,尽管具体细节可能略有不同。
他们通常根据对象通常存在的时间将对象使用的内存区域(称为堆)分成不同的部分。这些部分称为世代。这些部分称为老一代(或终身)和年轻一代。年轻代进一步分为称为伊甸园和幸存者空间的部分。具有单独世代的基本原理是许多对象只在很短的时间内使用。
CMS收集器
CMS 收集器是第一个并发收集器。与其他算法一样,CMS 在次要 GC 期间停止所有应用程序线程,该次要GC 是通过多个线程执行的。
并发标记清除 (CMS) 收集器(也称为并发低暂停收集器)收集终身代。它尝试通过与应用程序线程同时执行大部分垃圾收集工作来最大程度地减少由于垃圾收集而导致的暂停。通常,并发低暂停收集器不会复制或压缩活动对象。垃圾收集是在不移动活动对象的情况下完成的。如果碎片成为问题,我们需要分配更大的堆。
它的目的是最大限度地减少垃圾收集周期期间的暂停,使其适合需要低延迟(最小滞后或延迟)的应用程序。 CMS 没有停止所有应用程序线程,而是尝试与应用程序线程同时运行。当应用程序继续运行时,它会识别未使用的对象(标记)。后来,在一次单独的停止世界暂停(清扫)中,它回收了那些未使用的对象占用的内存。
CMS的局限性之一是所谓的“浮动垃圾”,即标记和清除发生在不同的时间,某些对象可能在应用程序执行期间变得不可达,但在初始标记阶段仍被标记为可达,并且没有被回收直到下一个收集周期。
除此之外,与应用程序线程同时运行垃圾收集可能会消耗更多的处理能力。
Garbage First(G1) 垃圾收集器
G1 GC(即 Garbage-First Garbage Collector)是 Java 7 Update 4 中引入的一种相对较新的垃圾收集器类型,并成为 Java 9 中的默认收集器。它旨在解决以前收集器的一些限制,并具有以下几个优点:
对象使用的内存区域(称为堆)被划分为更小的区域。每个区域都可以是年轻代(对于新对象)或老年代(对于长寿命对象)的一部分。
G1 首先优先收集年轻代中的区域,因为它们往往有更多的垃圾。但是,如果有必要,它也可以收集部分老年代。这使得 G1 能够专注于仅收集最有可能包含垃圾的区域,从而提高效率。
虽然 G1 主要与应用程序线程并发工作(这意味着它可以在程序仍在运行时运行垃圾收集任务),但在特定情况下它可能会使用短暂的停止世界暂停。这些停顿通常比老收集器要短得多。
Z 垃圾收集器
ZGC(或 Z Garbage Collector)是 Java 11 (JEP 333) 中引入的一个相对较新的实验性收集器,作为可选功能。它在 Java 15 中已做好上线准备,并拥有一些令人感到惊奇的功能:
ZGC 专为需要闪电般性能的应用程序而构建。它将暂停时间保持在 10 毫秒以下,并且可以处理大量内存,使其成为实时系统和大数据处理的理想选择。
ZGC不是Java中默认的收集器,需要显式启用(-XX:+UseZGC.)。 ZGC 最重要的调整选项是设置最大堆大小 (-Xmx)
ZGC 通常在内存越大时性能越好。然而,保持平衡并避免浪费资源很重要。
除了设置内存大小之外,ZGC 还让您可以控制其清理过程。您可以调整并发垃圾收集线程的数量(使用 -XX:ConcGCThreads 选项)以潜在地微调性能。
您可以调整 GC 使用的处理能力。 GC 的 CPU 时间过多会减慢应用程序的速度,而过少的 CPU 时间会导致未使用数据的堆积。这是为了找到适当的平衡。
在 Java 21 中,它已演变为分代 GC (JEP 439)。
要使用 GenZGC 需要传递两个 VM 参数
-XX:+使用ZGC -XX:+ZGenerational
分代 ZGC 旨在提高应用程序性能,通过为新对象和旧对象维护单独的代来扩展现有的 ZGC。
我们的目标是在非分代方法已经提供的功能之上添加这些优点:闪电般的快速暂停(不到一毫秒!),支持令人难以置信的大堆(想想太字节!),以及所需的最少设置。
区分分代 ZGC 与非分代 ZGC 以及其他垃圾收集器的重要设计概念:
- 没有多重映射内存
经典 ZGC 采用一种称为多重映射的技术,其中多个虚拟内存范围映射到同一物理内存范围。
分代 ZGC 依赖于传统方法,其中堆中的每个虚拟内存范围与物理内存范围一一对应。
- 优化障碍
存储屏障是垃圾收集 (GC) 中的基本概念。它们是编译器或运行时系统在程序执行期间插入的小代码片段。它们的目的是确保垃圾收集器对程序对象使用的内存有一致的视图。
分代 ZGC (ZGC) 采用多种技术来优化存储屏障,旨在最大限度地减少其性能影响,同时保持准确的垃圾收集。
- 双缓冲记忆集
双缓冲记忆集是垃圾收集中使用的一种特定优化技术,特别是在 Java 中引入的 Z 垃圾收集器 (ZGC) 中。它们在有效跟踪各代对象之间的引用以及最小化与存储屏障相关的开销方面发挥着至关重要的作用。
- 无需额外堆内存的重定位
这是指允许 GC 在收集周期内移动(或重新定位)现有堆空间内的对象而无需分配额外内存的技术。这对于提高效率、减少低延迟垃圾收集场景中的停顿特别有利。
- 密集堆区域
这些是堆中包含高比例活动对象(程序仍在使用的对象)的区域。它们本质上是“挤满”尚未被垃圾收集的对象的。
在垃圾收集周期中,ZGC 分析每个堆区域的密度。此分析有助于 ZGC 决定处理每个区域的最有效方法。
- 大对象
ZGC 已经可以很好地处理大对象。在分代中,ZGC 更进一步,允许在年轻代中分配大对象。鉴于区域可以在不重新定位的情况下老化,因此不需要在老一代中分配大对象只是为了防止昂贵的重新定位。相反,如果它们寿命较短,则可以在年轻代中收集它们;如果寿命较长,则可以廉价地将它们提升到老年代。
- 代际间的参考
世代 ZGC 通过搭载年轻一代集合来避免不断跟踪年轻一代到老年的参考。这种组合方法有效地识别和保留程序跨代仍然需要的对象。
如何选择 JVM GC
为您的 Java 应用程序选择正确的垃圾收集器 (GC) 需要了解您的特定需求以及 JVM 提供的不同 GC 算法的特征。
在大多数情况下,G1GC 是一个很好的起点,因为它提供了性能和暂停时间之间的平衡。
如果您主要关心的是高内存吞吐量,并且可以接受暂停,那么并行 GC 可能是一个不错的选择。对于超低延迟要求,请探索 ZGC(考虑其实验性质)。
您可以使用 JVM 标志微调某些 GC 算法的行为。但是,通常建议从默认设置开始,仅在必要时进行调整,且通过使用分析工具来分析应用程序的内存使用模式和 GC 行为。