ZGC 是个啥玩意?

描述

JDK 11 加入了实验性质的ZGC,相关JEP见:JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)

首先我们来了解一下G1G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用,作为体验版随着JDK 6u14版本面世,在JDK 7u4版本发行时被正式推出,相信熟悉JVM的同学们都不会对它感到陌生。在JDK 9中,G1被提议设置为默认垃圾收集器。见JEP 248: Make G1 the Default Garbage Collector

但是对于一些延迟敏感的场景,G1还是有些不太能满足需求,比如常见的游戏,尤其是实时类的网游。

GC性能测试

128G的堆,复合模式下的性能,看GC的停顿时间。

ZGC
                avg: 1.091ms (+/-0.215ms)
    95th percentile: 1.380ms
    99th percentile: 1.512ms
  99.9th percentile: 1.663ms
 99.99th percentile: 1.681ms
                max: 1.681ms

G1
                avg: 156.806ms (+/-71.126ms)
    95th percentile: 316.672ms
    99th percentile: 428.095ms
  99.9th percentile: 543.846ms
 99.99th percentile: 543.846ms
                max: 543.846ms

结论:使用ZGC可以稳定在2ms以内!

使用指南

本文不探讨ZGC的技术实现与原理,只记录相关使用方法。

实验阶段的ZGC目前不支持Windows,以下使用方法仅在Linux经过测试。

下载JDK

下载 Linux/x64 下使用的JDK 11

快速使用

通过-XX:+UnlockExperimentalVMOptions -XX:+UseZGC启用ZGC

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC

通过-Xmx<size>设置最大堆大小。

ZGC是并发收集器,必须指定最大堆大小。

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx<size>

(可选)通过-XX:ConcGCThreads=<number>设置并发线程数。

ZGC具有探索式的线程数自动选择功能,通常运行良好。线程数越多,GC占用CPU越多时间。线程数过少,GC可能收集的速度低于程序分配的速度。

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx<size> -XX:ConcGCThreads=<number>

通过-Xlog启用GC日志记录

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx<size> -Xlog:gc
# 帮助 -Xlog:help

启用大页面(Huge Page)

配置ZGC使用大页面一般会有更好的性能(吞吐量、延迟和启动时间等),并且没有明显的缺点,除了设置稍微复杂一点,配置过程通常需要root权限,所以默认情况下并未启用。

Linux/x86 上大页面默认大小为2MB

假设现在需要一个16GJava堆。即需要 16G / 2M = 8192 个大页面。

因为JVM中不仅GC会使用Java堆JVM中各种内部的数据结构也会使用,所以一般这个估计值需要偏大。在下面的示例中,将宽限2G的大页面。(18G / 2M = 9216)

配置系统的大页面池(需要root权限)

$ echo 9216 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

注意:如果内核找不到足够大的空闲大页面来满足需求,则无法保证上述命令成功执行。另外,内核可能需要一些时间来处理这个请求。

检查分配给池的数量,以确保请求成功并已完成。

$ cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# 9216

注意:如果Linux内核 >= 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:+UnlockExperimentalVMOptions -XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages ...

如果有多个可访问的hugetlbfs文件系统,那么使用-XX:ZPath指定要使用的路径。

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages -XX:ZPath=/hugepages ...