Skip to content

JEP 351: ZGC: Uncommit Unused Memory (Experimental) | ZGC:释放未使用的内存

摘要

增强 ZGC 以将未使用的堆内存归还给操作系统。

动机

ZGC 当前不会取消提交并将内存归还给操作系统,即使这些内存已经很长时间没有使用。这种行为并不是所有类型的应用和环境的最优选择,尤其是那些关注内存占用的场景。例如:

  • 容器化环境,其中资源使用需要付费。

  • 环境中的应用程序可能长时间处于空闲状态,并且与其他许多应用程序共享或竞争资源。

  • 一个应用程序在执行过程中可能具有非常不同的堆空间需求。例如,启动期间所需的堆可能大于稳态执行期间所需的堆。

HotSpot 中的其他垃圾收集器,如 G1 和 Shenandoah,目前已经提供了这种能力,某些类别的用户发现这非常有用。将这一功能添加到 ZGC 也会受到同样一组用户的欢迎。

描述

ZGC 堆由一组称为 ZPages 的堆区域组成。每个 ZPage 与可变数量的已提交堆内存相关联。当 ZGC 压缩堆时,ZPages 将被释放并插入到页面缓存 ZPageCache 中。页面缓存中的 ZPages 准备被重新使用以满足新的堆分配需求,在这种情况下,它们将从缓存中移除。页面缓存对于性能至关重要,因为提交和取消提交内存是昂贵的操作。

页面缓存中的 ZPages 集合代表了堆的未使用部分,这些部分 可以 被取消提交并归还给操作系统。因此,取消提交内存可以通过从页面缓存中驱逐一个精心选择的 ZPages 集合,并取消提交与这些页面相关联的内存来实现。页面缓存已经按照最近最少使用(LRU)的顺序和大小(小、中、大)进行了分割,因此驱逐 ZPages 和取消提交内存的机制相对直接。挑战在于设计决定何时从缓存中驱逐 ZPage 的策略。

一个简单的策略是设置一个超时或延迟值,指定 ZPage 在页面缓存中可以保留多久才会被驱逐。这个超时将有一个合理的默认值,并可以通过命令行选项进行覆盖。Shenandoah GC 使用了类似的策略,默认值为 5 分钟,并提供了命令行选项 -XX:ShenandoahUncommitDelay=<milliseconds> 来覆盖默认值。

上述策略可能效果还不错。但是,人们也可以设想出更复杂的策略,这些策略并不涉及添加新的命令行选项。例如,基于垃圾收集频率或其他数据找到合适的超时值的一些启发式算法。我们最初将提供一个简单的超时策略,并附带一个 -XX:ZUncommitDelay=<seconds> 选项,如果找到更复杂的策略,则随后再推出。

默认情况下将启用“取消提交”功能。但是,无论策略如何决定,ZGC 都不应该取消提交内存,导致堆内存低于其最小大小(-Xms)。这意味着如果 JVM 以等于最大堆大小(-Xmx)的最小堆大小(-Xms)启动,则“取消提交”功能实际上将被禁用。此外,还将提供 -XX:-ZUncommit 选项来明确禁用此功能。

最后,Linux/x64 上的 ZGC 使用 tmpfs 或 hugetlbfs 文件来支持堆。取消提交这些文件使用的内存需要支持 fallocate(2)FALLOC_FL_PUNCH_HOLE 的功能,该功能首次出现在 Linux 3.5(tmpfs)和 4.3(hugetlbfs)中。在较旧的 Linux 内核上运行时,ZGC 应继续像之前那样工作,只是“取消提交”功能将被禁用。

测试

  • 将开发一个或多个 jtreg 测试来验证“取消提交”功能。
  • 将使用现有的基准测试(如 SPECjbb 和 SPECjvm)来验证,在使用默认策略时,我们是否没有看到可测量的延迟或吞吐量回归。