JEP 351:ZGC:退还未使用的内存(实验性)
概述
增强 ZGC 以将未使用的堆内存返回给操作系统。
动机
ZGC 当前不会取消提交并将内存返还给操作系统,即使该内存长时间未被使用。这种行为并非对所有类型的应用程序和环境都是最优的,尤其是那些对内存占用较为敏感的场景。例如:
-
按使用量付费的容器环境。
-
应用程序可能长时间处于空闲状态,并且与其他许多应用程序共享或争夺资源的环境。
-
应用程序在执行过程中可能对堆空间的需求差异很大。例如,启动期间所需的堆空间可能大于稳定状态执行期间所需的空间。
HotSpot 中的其他垃圾收集器,例如 G1 和 Shenandoah,目前已经提供了这种能力,某些类别的用户发现这非常有用。将这种能力添加到 ZGC 会受到相同用户群体的欢迎。
描述
ZGC 堆由一组称为 ZPages 的堆区域组成。每个 ZPage 都关联着一个可变数量的已提交堆内存。当 ZGC 压缩堆时,ZPages 会被释放并插入到一个页面缓存中,即 ZPageCache。页面缓存中的 ZPages 已准备好被重用来满足新的堆分配需求,在这种情况下它们会从缓存中移除。页面缓存对性能至关重要,因为提交和取消提交内存是非常耗时的操作。
页面缓存中的 ZPages 集合代表了堆中未使用的部分,这些部分可以被解除提交并归还给操作系统。因此,通过简单地从页面缓存中驱逐一组精心挑选的 ZPages,并解除与这些页面相关的内存的提交,就可以实现内存的解除提交。页面缓存已经按照最近最少使用(LRU)顺序将 ZPages 排列,并按大小(小、中、大)进行分类,因此驱逐 ZPages 和解除内存提交的机制相对简单。挑战在于设计一种策略,用于决定何时应该从缓存中驱逐一个 ZPage。
一个简单的策略是设置一个超时或延迟值,用于指定 ZPage 在页面缓存中可以停留多长时间后被驱逐。这个超时值会有一个合理的默认值,并且可以通过命令行选项来覆盖它。Shenandoah 垃圾回收器使用了类似的策略,默认值为 5 分钟,并通过命令行选项 -XX:ShenandoahUncommitDelay=<毫秒>
来覆盖默认值。
像上面这样的策略可能效果不错。然而,也可以设想更复杂的策略,而无需添加新的命令行选项。例如,根据 GC(垃圾回收)频率或其他一些数据来找到合适的超时值的启发式方法。我们将首先提供一个简单的超时策略,并带有 -XX:ZUncommitDelay=<seconds>
选项,而更复杂的策略(如果有的话)则留待以后实现。
未提交(uncommit)功能将默认启用。但是无论策略如何决定,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 测试,以验证未提交(uncommit)功能。
-
将使用现有的基准测试(例如 SPECjbb 和 SPECjvm),来验证在使用默认策略时不会出现可测量的延迟或吞吐量下降。