使用 ZGC 中的自动堆大小调整优化内存利用率


此处展示的工作是作为 Oracle、乌普萨拉大学和 KTH 之间的联合研究项目 的一部分进行的。在 inside.java 上关注博客系列,以阅读更多关于在斯德哥尔摩 Oracle 开发办公室进行的 JVM 研究的信息。


我叫 Marina Shimchenko,目前正在乌普萨拉大学攻读计算机科学博士学位。在整个博士学习期间,我都有幸与斯德哥尔摩的 Oracle 工程师合作。我的研究小组与 Oracle 之间的这种合作对于扩展我在研究领域的知识和专业知识至关重要。我即将展示的项目是这种卓有成效的合作的成果之一

简介

有效的堆大小管理对于优化 Java 应用程序中的内存利用率和性能至关重要。但是,手动配置堆大小限制会带来挑战。首先,它既费时又低效,而且还难以考虑内存使用情况的动态变化。在这篇博文中,我们介绍了一种用于 ZGC 中堆大小调整的创新自动化策略,利用 CPU 利用率作为“调整旋钮”来优化性能和内存使用情况。

堆大小调整中的挑战

确定理想的堆大小很复杂,尤其要考虑硬件配置等各种参数。我们的研究表明,即使是在具有不同内核配置的同一台机器上,堆大小也会在机器之间变化,且没有明显模式。这些变化需要对堆大小进行重大更改,突出了手动堆大小调整的局限性。

引入自动堆大小调整

我们对堆大小调整的方法基于 CPU 利用率。我们不是通过设置堆大小来控制应用程序应执行多少 GC,而是让 GC 使用与应用程序 CPU 使用率成比例(用户定义)的 CPU。因此,如果应用程序的 CPU 使用率增加(通常会增加其分配率),那么 GC 的目标 CPU 也会增加。

工作原理

在 ZGC 中实现自适应堆大小调整利用了现有的软最大堆大小限制。此限制会触发垃圾回收,而不会导致停顿,直到达到 Xmx(最大堆)参数。在每个垃圾回收周期结束时,将调整软最大堆以满足程序员设置的目标 GC CPU 开销。GC CPU 开销通过比较应用程序线程和 GC 线程所花费的时间来衡量(参见图 1)。由于 ZGC 作为完全并发垃圾回收器的独特特性,我们在 ZGC 中实现了自适应堆大小调整。使用 ZGC,垃圾回收暂停非常小,并且收集器永远不会处于关键路径上。这使我们能够自信地减小堆大小,而不会对延迟产生任何重大影响。

图 1 的标题。我们实现中的 GC 和应用程序时间的具体测量。在 GC 周期 n+1 (t=7.5) 结束时,我们考虑在 GC 线程(蓝色)中花费的时间和在 mutator(绿色)中花费的时间。灰色线条表示在 GC 周期 n 结束时测量的时间。我们只包括 mutator 被调度的时刻,这意味着应用程序时间=2.5+2+2.5=7。在 GC 时间的情况下,我们从 GC 周期的开始到结束进行测量。因此,GC 时间=3* 1.5=4.5,即使第 2 个 GC 线程在 t=7 之后未被调度。因此,GC CPU 开销=4.5/7=~64%。 (这异常高 — 在大多数应用程序中,GC 占用的比例不到 5%。)

性能评估

为了评估自动堆调整的影响,我们在各种基准测试中对多个 GC 目标(5%、10%、15% 和 20%)进行了测试。调整 GC 目标会显著影响内存使用情况,更高的目标会降低应用程序的内存使用情况,而较低的目标会增加内存使用情况。执行时间通常不受影响,但特定基准测试可能会显示出改进或下降。内存使用情况和能耗之间的关系很复杂,更高的 GC 目标会因更高的 CPU 使用率而增加能耗。然而,在超额承诺的 CPU 环境中,减少内存使用量可以降低能耗。自适应方法对延迟有积极或中性的影响,它会降低 CPU 密集型工作负载中的延迟。

结论

我们的自动堆大小调整策略为手动堆大小调整的挑战提供了一个积极主动的解决方案。通过让开发人员能够控制垃圾回收的 CPU 利用率,我们消除了跟踪内存使用模式和手动确定堆大小的需要。这种方法减少了内存浪费,简化了开发体验。通过采用自动堆大小调整,开发人员可以在享受简化的内存管理的同时释放其应用程序的全部潜力。