关于ZGC
ZGC(Z Garbage Collector)是一种可扩展的低延迟垃圾回收器,旨在满足以下目标:
- 亚毫秒(Sub-millisencond)级的最大暂停时间
- 暂停时间不会随着heap、live-set、root-set的增大而增加
- 可以处理8MB到16TB的堆大小
ZGC支持:
- 并发(Concurrent)
- 基于Region
- 压缩(Compacting)
- NUMA-aware
- 使用着色指针
- 使用负载屏障
ZGC的核心是一个并发垃圾收集器,这意味着所有繁重的工作都在Java线程执行的同时完成。这极大地限制了垃圾收集对应用程序响应时间的影响。
JVM如何设置
JVM一般通过JAVA_OPTS环境变量设置,如果使用Tomcat,可以使用CATALINA_OPTS设置。JAVA_OPTS与CATALINA_OPTS的不同是:
- [JAVA_OPTS]: (optional) Java runtime options used when the “start”, “stop” or “run” command is executed
- [CATALINA_OPTS]: (optional) Java runtime options used when the “start” or “run” command is executed
支持的平台
Platform | Supported | Since | Comment |
---|---|---|---|
Linux/AArch64 | 支持 | JDK 13 | |
Linux/x64 | 支持 | JDK 11 | |
macOS | 支持 | JDK 14 | |
Windows | 支持 | JDK 14 | Requires Windows version 1803 (Windows 10 or Windows Server 2019) or later. |
快速开始
如果您是第一次尝试 ZGC,请从使用以下 GC 选项开始:
-XX:+UseZGC -Xmx
-Xlog:gc
如需更详细的日志记录,请使用以下选项:(在VSCode中加*,启动报错)
-XX:+UseZGC -Xmx
-Xlog:gc*
示例代码:
JAVA_OPTS=”-XX:+UseZGC -Xmx1024m -Xlog:gc”
配置和调优
General GC Options
- -XX:MinHeapSize, -Xms
- -XX:InitialHeapSize, -Xms
- -XX:MaxHeapSize, -Xmx
- -XX:SoftMaxHeapSize
- -XX:ConcGCThreads
- -XX:ParallelGCThreads
- -XX:UseLargePages
- -XX:UseTransparentHugePages
- -XX:UseNUMA
- -XX:SoftRefLRUPolicyMSPerMB
- -XX:AllocateHeapAt
ZGC Options
- -XX:ZAllocationSpikeTolerance
- -XX:ZCollectionInterval
- -XX:ZFragmentationLimit
- -XX:ZMarkStackSpaceLimit
- -XX:ZProactive
- -XX:ZUncommit
- -XX:ZUncommitDelay
ZGC Diagnostic Options (-XX:+UnlockDiagnosticVMOptions)
- -XX:ZStatisticsInterval
- -XX:ZVerifyForwarding
- -XX:ZVerifyMarking
- -XX:ZVerifyObjects
- -XX:ZVerifyRoots
- -XX:ZVerifyViews
启用ZGC
使用-XX:+UseZGC
参数启用ZGC。
设置Heap大小
ZGC最重要的调优选项是设置最大堆(Heap)大小 (-Xmx<size>
)。由于ZGC是并发收集器,因此必须选择最大堆大小:
- 堆可以容纳应用程序的live-set,
- 在GC运行期间堆中有足够的空间分配给应用程序。
需要多少空间取决于应用程序的分配率和实时设置大小。一般来说,你给ZGC的内存越多越好。但同时,浪费内存也是不可取的,所以这一切都是为了在内存使用和GC需要运行的频率之间找到平衡。
设置并发GC线程数
第二个调优选项是设置并发 GC 线程的数量 (-XX:ConcGCThreads=<number>
)。 ZGC 有启发式自动选择这个数字。这种启发式通常效果很好,但根据应用程序的特性,这可能需要进行调整。这个选项本质上决定了应该给 GC 多少 CPU 时间。给它太多,GC 会从应用程序中窃取太多 CPU 时间。给它太少,应用程序可能会比 GC 收集垃圾的速度更快地收集垃圾。
一般来说,如果低延迟(即低应用程序响应时间)对您的应用程序很重要,那么永远不要过度配置您的系统。理想情况下,您的系统的 CPU 利用率不应超过 70%。
将未使用的内存归还给操作系统
默认情况下,ZGC 不提交未使用的内存给操作系统。这对于关注内存占用的应用程序和环境很有用。可以使用 -XX:-ZUncommit
禁用此功能。此外,内存不会未提交,因此堆大小会缩小到最小堆大小 (-Xms) 以下。这意味着如果最小堆大小 (-Xms) 配置为等于最大堆大小 (-Xmx),则此功能将被隐式禁用。
可以使用-XX:ZUncommitDelay=<senconds>
(默认为 300 秒)配置取消提交延迟。此延迟指定内存在有资格取消提交之前应该被使用多长时间。
在 Linux 上,取消提交未使用的内存需要具有 FALLOC_FL_PUNCH_HOLE 支持的 fallocate(2),它首先出现在内核版本 3.5(用于 tmpfs)和 4.3(用于 Hugetlbfs)中。
在Linux上使用Large Pages
将 ZGC 配置为使用大页面通常会产生更好的性能(在吞吐量、延迟和启动时间方面)并且没有真正的缺点,只是设置稍微复杂一些。设置过程通常需要 root 权限,这就是默认情况下不启用它的原因。
在 Linux/x86 上,large pages(也称为“huge pages”)的大小为 2MB。
假设您想要一个 16G 的 Java 堆。这意味着您需要 16G / 2M = 8192 个大页面。
首先为大页面池分配至少 16G(8192 页)的内存。 “至少”部分很重要,因为在 JVM 中启用大页面意味着不仅 GC 将尝试将这些用于 Java 堆,而且 JVM 的其他部分将尝试将它们用于各种内部数据结构(代码堆、标记位图等)。因此,在此示例中,我们将保留 9216 个页面 (18G) 以允许 2G 的非 Java 堆分配以使用大页面。
配置系统的大页面池,使其拥有所需数量的页面(需要root权限):
$ echo 9216 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
请注意,如果内核找不到足够的空闲大页面来满足请求,则不能保证上述命令会成功。另请注意,内核处理请求可能需要一些时间。在继续之前,请检查分配给池的大页面数量以确保请求成功并已完成。
$ cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
9216
如果您使用的是
Linux kernel >= 4.14
内核,则可以跳过下一步(您挂载 Hugetlbfs 文件系统的位置)。否则,如果您使用的是较旧的内核,则 ZGC 需要通过 Hugetlbfs 文件系统访问大页面。
挂载一个hugetlbfs 文件系统(需要root 权限)并使运行JVM 的用户可以访问它(在本例中,我们假设该用户的uid 为123)。
$ mkdir /hugepages
$ mount -t hugetlbfs -o uid=123 nodev /hugepages
现在使用-XX:+UseLargePages
选项启动 JVM。
$ java -XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages ...
如果有多个可访问的 Hugetlbfs 文件系统可用,那么(并且只有这样)您还必须使用-XX:AllocateHeapAt
来指定要使用的文件系统的路径。例如,假设安装了多个可访问的hugetlbfs 文件系统,但您特别希望使用它的文件系统安装在/hugepages 上,然后使用以下选项。
$ java -XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages -XX:AllocateHeapAt=/hugepages ...
除非采取必要的措施,否则巨页池的配置和 Hugetlbfs 文件系统的安装在重新启动后会丢失。
在Linux上启用Transparent Huge Pages
使用显式大页面(如上所述)的替代方法是使用透明大页面。对于延迟敏感的应用程序,通常不推荐使用透明大页面,因为它往往会导致不必要的延迟峰值。但是,可能值得尝试一下,看看您的工作负载是否/如何受到它的影响。但请注意,您的里程可能会有所不同。
在 Linux 上,在启用透明大页面的情况下使用 ZGC 需要
kernel >= 4.7
。
使用以下选项在 VM 中启用透明大页面:
-XX:+UseLargePages -XX:+UseTransparentHugePages
这些选项告诉 JVM 为其映射的内存发出 madvise(…, MADV_HUGEPAGE) 调用,这在 madvise 模式下使用透明大页面时很有用。
要启用透明大页面,您还需要通过启用 madvise 模式来配置内核。
$ echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
$ echo advise > /sys/kernel/mm/transparent_hugepage/shmem_enabled
启用NUMA支持
ZGC 具有 NUMA 支持,这意味着它会尽量将 Java 堆分配定向到 NUMA 本地内存。默认启用此功能
。但是,如果 JVM 检测到它绑定到系统中的 CPU 子集,它将自动禁用。通常,您无需担心此设置,但如果您想明确覆盖 JVM 的决定,您可以使用-XX:+UseNUMA
或-XX:-UseNUMA
选项来实现。
在 NUMA 机器(例如多路 x86 机器)上运行时,启用 NUMA 支持通常会显着提升性能。
启用GC Logging
使用以下命令行选项启用 GC 日志记录:
-Xlog:<tag set>,[<tag set>, ...]:<log file>
有关此选项的一般信息/帮助:
-Xlog:help
要启用基本日志记录(每个 GC 输出一行):
-Xlog:gc:gc.log
要启用对调优/性能分析有用的 GC 日志记录:
-Xlog:gc*:gc.log
其中 gc* 表示记录包含 gc 标记的所有标记组合,而 :gc.log 表示将日志写入名为 gc.log 的文件。