Java ZGC 调优

Java ZGC 调优

ZGC 是一种专门的垃圾回收器,主要用于管理大型堆和尽量减少 Java 应用程序中的停顿。它能应对在内存密集型工作负载和一致的响应时间至关重要的情况下的垃圾回收的挑战。ZGC 利用并发处理能力和先进的算法,为优化现代 Java 应用程序的性能提供了有效的解决方案。在本篇文章中,将专门探讨调整 ZGC 以提高性能的技术。不过,如果想了解更多基础知识,可以观看在 JAX 伦敦会议上发表的 垃圾回收调优 讲座。

如何启用 ZGC?

确保使用的 Java 版本支持 ZGC。OpenJDK 从 JDK11 开始支持 ZGC。在启动应用程序时添加以下 JVM 参数,这样就可以在 Java 应用程序中启用 ZGC 垃圾收集器:

# D瓜哥 · https://www.digauge.com
-XX:+UseZGC -XX:+ZGenerational (1)
1D瓜哥注:分代 ZGC 从 OpenJDK 21+ 开始支持。

何时使用 ZGC?

如果应用符合其中任何一项要求,就可以考虑使用 ZGC:

  1. 堆大小较大:ZGC 特别适合堆容量较大的应用程序,堆容量通常在数十 GB 或更大。如果应用需要大量内存,ZGC 的低延迟特性将使其成为一个令人信服的选择。

  2. 低延迟要求:当应用需要一致的响应时间和低延迟性能时,ZGC 将大显身手。在需要最大限度缩短垃圾回收暂停时间的情况下,ZGC 表现出色,特别适合交互式应用和实时性应用。

  3. 具有不同工作负载的应用:ZGC 专为处理不同的工作负载而设计,因此适用于内存使用模式不可预测的应用。无论应用程序经历的是周期性的,突发性的,还是富有变化性的负载,ZGC 都能有效地适应这些调整。

ZGC 调优参数

ZGC 是 Java 中的一种垃圾收集器,它采用了一种不同的调优方法:将暴露的 JVM 参数数量降至最低。与需要细粒度调整的传统垃圾收集器不同,ZGC 专注于优化大型堆的管理,同时以最小的配置开销提供高效的垃圾收集。这种精简的方法允许开发人员主要关注一个关键的 JVM 调整参数:堆大小。

.1. 堆大小 (-Xmx<size>)

“堆大小”参数是 ZGC 的一个重要调优选项。它决定了分配给 Java 堆的最大内存量,这是 Java 应用程序执行期间,对象存储在内存中的位置。

在配置 ZGC 的堆大小时,有几个因素需要考虑。首先,需要确保堆能够容纳应用程序的存活对象集合,其中包括在运行期间主动使用的所有对象。分配过小的堆大小可能会导致频繁的垃圾回收和暂停时间增加,因为 ZGC 需要更频繁地运行以回收内存。

另一方面,分配过大的堆大小也会导致内存资源的浪费。在内存使用和垃圾回收频率之间取得平衡非常重要。具体的最佳堆大小取决于各种因素,如应用程序的内存需求、存活对象集合的大小以及系统的整体内存可用性。

要指定堆大小,请在启动 Java 应用程序时使用 -Xmx<size> 参数,其中 <size> 表示所需的堆大小。例如,-Xmx32g 将最大堆大小设置为 32 千兆字节。

.2. 并发 GC 线程数 (-XX:ConcGCThreads=<number>)

另一个值得考虑的调整选项是 ZGC 中并发垃圾回收(GC)线程的数量,可以使用 -XX:ConcGCThreads=<number> 参数进行配置。ZGC 内置了启发式方法,可根据应用程序的特性自动选择最佳线程数。ZGC 中的默认启发式通常在大多数情况下都很有效。不过,根据应用程序的具体行为和要求,可能需要调整并发 GC 线程的数量。该参数决定了分配给垃圾回收器的 CPU 时间。分配过多的线程会导致 GC 占用过多的 CPU,从而占用应用程序的宝贵资源。另一方面,分配过少的线程可能会降低 GC 性能。

从 JDK 17 开始,ZGC 引入了并发 GC 线程数量的动态缩放。这意味着 ZGC 可以根据工作量自动调整线程数量,从而减少了手动调整该参数的可能性。

.3. 启用大页面 (-XX:+UseLargePages)

配置 ZGC 以使用大页面可以提高吞吐量、减少延迟并缩短启动时间。在 Linux/x86 系统上,大页(也称为超大页)的大小为 2MB。大页是指比标准页大小更大的内存页。它们具有减少内存管理开销和提高内存访问效率等优点。

在 ZGC 中启用大页面,需要在 JVM 中配置 -XX:+UseLargePages 选项。

启用大页面需要在操作系统级别进行某些配置。这些配置(如为大页池分配内存和设置 hugetlbfs 文件系统)不在本帖讨论范围之内。

.4. 启用透明大页 (-XX:+UseTransparentHugePages)

除了使用显式大内存页(如上所述),另一种方法是使用透明大页(简称:THP)。THP 是 Linux 内核中的一个特性,它能自动将标准内存页聚合为更大、更高效的超大页。THP 的目的是通过减少与管理单个页面相关的开销来改进内存管理。通过将多个标准页面组合成单个超大页面(通常为 2MB 大小),THP 有可能提高性能。

在 JVM 中启用透明巨页,可以使用 -XX:+UseTransparentHugePages 选项。这样,Java 应用程序就能利用操作系统管理的大型聚合内存页。值得注意的是,THP 在某些情况下可能会引入延迟峰值,因此不太适合对延迟敏感的应用程序。在启用 THP 之前,建议评估其对特定工作负载和性能要求的影响。

在内核级别配置和管理透明巨页可能需要额外的步骤,具体细节不在本帖讨论范围之内。

.5. 启用 NUMA 支持(-XX:+UseNUMA

ZGC 支持 NUMA,这意味着它会尽力将 Java 堆分配引导到 NUMA 本地内存。NUMA 是非统一内存访问(Non-Uniform Memory Access)的缩写,指的是多插槽系统中使用的架构设计。在 NUMA 系统中,内存被划分为多个内存节点,每个节点与特定的处理器或插槽相关联。与访问远程内存节点相比,每个处理器都能更快地访问自己的本地内存节点。

默认情况下,ZGC 会启用 NUMA 支持,从而充分利用 NUMA 架构的优势。它会自动检测并利用本地内存节点,以优化内存访问并提高性能。不过,如果 JVM 检测到它必须使用单个 NUMA 节点上的内存,NUMA 支持将被禁用。

在大多数情况下,不需要显式配置 NUMA 支持。不过,如果想推翻 JVM 的决定,可以使用以下选项:

  • 显式启用 NUMA 支持:-XX:+UseNUMA

  • 明确禁用 NUMA 支持:-XX:-UseNUMA

NUMA 支持在多插槽 x86 机器或其他采用 NUMA 架构的系统中尤为重要。在单插槽或非 NUMA 系统中,NUMA 支持可能不会对性能产生重大影响。

.6. 6.将未使用的内存归还给操作系统(-XX:+ZUncommit

ZGC 的设计可有效管理大容量的堆。在应用程序不需要时,分配较大的堆大小,会导致内存使用效率低下。默认情况下,ZGC 会取消未使用内存的提交,并将其返回给操作系统。使用 -XX:-ZUncommit 可以禁用这一功能。

如果堆大小低于指定的最小堆大小 (-Xms),ZGC 会确保内存不会被取消提交。因此,如果将最小堆大小设置为与最大堆大小 (-Xmx) 一致,则会隐式禁用非提交功能。

为了灵活管理未提交内存,ZGC 允许使用 -XX:ZUncommitDelay=<seconds> 选项配置未提交延迟,默认延迟为 300 秒。该延迟指定了内存在符合取消提交条件之前保持未使用的持续时间。

允许 ZGC 在应用程序运行时提交和取消提交内存可能会影响应用程序的响应时间。如果使用 ZGC 的主要目的是实现极低的延迟,建议将最大堆大小 (-Xmx) 和最小堆大小 (-Xms) 设置为相同的值。此外,使用 -XX:+AlwaysPreTouch 选项也有好处,因为它可以在应用程序启动前预分页内存,优化性能并减少延迟。

调优 ZGC

研究 ZGC 性能特征的最佳方法是分析 GC 日志。GC 日志包含有关垃圾回收事件、内存使用情况和其他相关指标的详细信息。有几种工具可以帮助分析 GC 日志,如 GCeasyIBM GC & Memory visualizerGoogle Garbage cat。通过使用这些工具,可以直观地了解内存分配模式,识别潜在的瓶颈,并评估垃圾回收的效率。这样,在微调 ZGC 以获得最佳性能时,就能做出明智的决策。

总结

最后,本篇文章讨论了 ZGC 的各种 JVM 调整参数,旨在优化其在 Java 应用程序中的性能。通过利用这些调整选项,开发人员可以对 ZGC 进行微调,以根据其特定需求提供最佳性能。此外,仔细分析 GC 日志和监控 ZGC 的行为可以为了解其性能特征提供有价值的信息。通过尝试使用这些调整参数并密切监控 GC 日志,开发人员可以释放 ZGC 的全部潜能,并确保其 Java 应用程序中的垃圾回收效率。