一、简介

JVM 附带了各种垃圾收集选项,以支持各种部署选项。这样,我们就可以灵活地选择为我们的应用程序使用哪个垃圾收集器

默认情况下,JVM 根据主机的类选择最合适的垃圾收集器。然而,有时,我们的应用程序会遇到与 GC 相关的主要瓶颈,要求我们更好地控制使用哪种算法。问题是, “如何选择 GC 算法?”

在这篇文章中,我们试图回答这个问题。

2.什么是GC?

Java 是一种垃圾收集语言,我们无需为应用程序手动分配和释放内存。操作系统分配给 JVM 进程的整个内存块称为 。然后,JVM 将该堆分成两组,称为世代。这种细分使其能够应用多种技术来实现高效的内存管理。

年轻(伊甸园)一代 是分配新创建的对象的地方 。它通常很小(100-500MB)并且还有两个 幸存者空间老年代 是存储较旧或老化对象的地方 - 这些通常是长期存在的对象。这个空间比年轻一代要大得多。

收集器不断 跟踪年轻代的完整性 并触发小型收集,在此期间,活动对象被移动到幸存者空间之一,死亡对象被删除。如果一个对象在一定次数的 Minor GC 中幸存下来,收集器会将其移动到老年代。 当旧空间被认为已满时,会发生重大GC ,并且死对象将从旧空间中删除。

在每次 GC 期间,都会有一些 停止世界 阶段,在此期间不会发生任何其他事情 — 应用程序无法为任何请求提供服务。我们称这个 时间为暂停时间

3. 需要考虑的变量

尽管 GC 使我们免于手动内存管理,但它实现这一目标是有代价的。 我们的目标应该是保持 GC 运行时开销尽可能低 。有几个变量可以帮助我们决定哪个收集器最能满足我们的应用程序需求。我们将在本节的剩余部分中讨论它们。

3.1.堆大小

这是操作系统分配给 JVM 的工作内存总量。理论上来说, 内存越大,回收前可以保留的对象就越多,从而导致GC时间越长 。可以使用 -Xms= 设置最小和最大堆大小和 -Xmx= 命令行选项。

3.2.应用程序数据集大小

这是应用程序需要保留在内存中才能有效工作的对象的总大小。由于所有新对象都加载到年轻代空间中,这肯定会影响最大堆大小,从而影响 GC 时间。

3.3. CPU数量

这是机器可用的核心数量。这个变量直接影响我们选择哪种算法。有些算法仅在有多个核心可用时才有效,而其他算法则相反。

3.4.暂停时间

暂停时间是垃圾收集器停止应用程序以回收内存的持续时间。该变量直接影响延迟,因此目标是限制这些暂停中的最长暂停时间。

3.5.吞吐量

我们指的是进程实际执行应用程序工作所花费的时间。 应用程序时间与执行 GC 工作所花费的开销时间越长,应用程序的吞吐量就越高

3.6.内存占用

这是 GC 进程使用的工作内存。当设置的内存有限或进程较多时,此变量可能决定可扩展性。

3.7.迅速

这是对象死亡和其占用的内存被回收之间的时间。这与堆大小有关。理论上,堆大小越大,及时性越低,因为触发收集所需的时间越长。

3.8. Java版本

随着新的 Java 版本的出现,支持的 GC 算法和默认收集器通常会发生变化。我们建议从默认收集器及其默认参数开始。根据所选收集器的不同,调整每个参数会产生不同的效果。

3.9.潜伏

这是应用程序的响应能力。 GC 暂停直接影响该变量。

4. 垃圾收集器

除了串行 GC 之外,当有多个核心可用时,所有其他收集器都是最有效的:

4.1.串行 GC

串行收集器使用单个线程来执行所有垃圾收集工作。在某些小型硬件和操作系统配置上默认选择它,或者可以使用选项 -XX:+UseSerialGC 显式启用它。

优点:

  • 没有线程间通信开销,它相对高效。
  • 它适用于客户端级机器和嵌入式系统。
  • 它适合具有小数据集的应用程序。
  • 即使在多处理器硬件上,如果数据集很小(最多 100 MB),它仍然是最高效的。

缺点:

  • 对于具有大型数据集的应用程序来说效率不高。
  • 它无法利用多处理器硬件。

4.2.并行/吞吐量 GC

该收集器 使用多个线程来加速垃圾收集 。在 Java 版本 8 及更早版本中,它是服务器级计算机的默认设置。我们可以使用 -XX:+UseParallelGC 选项覆盖此默认值。

4.4. G1(垃圾优先)GC

G1像CMS一样使用多个后台GC线程来扫描和清除堆。实际上,核心 Java 团队将 G1 设计为对 CMS 的改进,通过额外的策略修补了它的一些弱点。

除了增量和并发收集之外, 它还跟踪以前的应用程序行为和GC暂停以实现可预测性 。然后,它首先专注于回收最有效区域的空间——那些大多充满垃圾的区域。出于这个原因,我们将其称为 “垃圾优先”

从 Java 9 开始,G1 是服务器级机器的默认收集器。我们可以通过在命令行上提供 -XX:+UseG1GC 来显式启用它。

优点:

  • 它对于巨大的数据集非常有效。
  • 它充分利用多处理器机器。
  • 这是实现暂停时间目标最有效的方法。

缺点:

  • 当有严格的吞吐量目标时,这不是最好的。
  • 它要求应用程序在并发收集时与GC共享资源。

G1 最适合具有非常严格的暂停时间目标和适度的总体吞吐量的应用程序,例如交易平台或交互式图形程序等实时应用程序。

4.5. Z 垃圾收集器 (ZGC)

ZGC 是一个可扩展的低延迟垃圾收集器。即使在多 TB 堆上,它也能保持较短的暂停时间。它使用的技术包括参考着色、重定位、负载屏障和重新映射。它非常适合服务器应用程序,其中大型堆很常见并且需要快速的应用程序响应时间。

它是在 Java 11 中作为实验性 GC 实现引入的。我们可以通过在命令行上提供 -XX:+UnlockExperimentalVMOptions -XX:+UseZGC 来显式启用它。有关更详细的描述,请访问我们关于 Z Garbage Collector 的文章。

5. 结论

对于许多应用程序来说,收集器的选择从来都不是问题,因为 JVM 默认值通常就足够了 。这意味着应用程序可以在垃圾收集存在且暂停频率和持续时间可接受的情况下良好运行。然而,对于一大类应用程序来说,情况并非如此,尤其是那些具有巨大数据集、许多线程和高事务率的应用程序。

在本文中,我们探讨了 JVM 支持的垃圾收集器。我们还研究了可以帮助我们选择适合应用程序需求的正确收集器的关键变量。