本篇教程由作者设定使用 CC BY-NC 协议。

声明

  • 本测试的结果和推荐仅对本次测试负责,仅供参考,使用时务必二次测试!

  • 本次测试使用 Azul Zulu OpenJDK 24.0.1+9 x64。低版本 JDK 或特定发行商的 JDK 可能不存在某些功能,导致在这些 JDK 中使用此测试中某些 JVM 参数时崩溃或不生效!

    包括以下: -XX:+UseCompactObjectHeaders (JEP 450)、-XX:ShenandoahGCMode=generational (JEP 404);以及 ShenandoahGC 在 Oracle JDK 的任何版本中都不存在,ShenandoahGC 详细的支持情况见此处

  • 不想看摆数据的直接抄“JVM 参数”和结语部分的参数使用即可。

测试前准备

前言

在翻 OpenJDK 24 的 JEP 的时候,偶然看到了“分代 Shenandoah”。一想,似乎没见过有人玩 Minecraft 用过 ShenandoahGC,基本都是直接推荐用 ZGC / 分代 ZGC。哪个才是最好?的遂进行本次测试。

测试环境

  • 物理内存 8G B DDR3,硬盘 SATA SSD,CPU 2 核 4 线程(AMD A10-5800K APU)

  • 系统 Windows 10 21H2 x86_64, IoT Enterprise LTSC;Java: Azul Zulu OpenJDK 24.0.1+9 x86_64

  • Minecraft 1.21.7,Fabric服务端,Mods:Fabric API、Spark、Carpet,8 视距 8 模拟

  • 没有测客户端,因为懒而且电脑性能过于先进直接卡爆

JVM 参数

如果你要在启动器中使用这些参数,你可能需要打开“不添加默认的 JVM 参数”之类的选项。

在每一项测试中,都添加以下 JVM 参数:

-server  -Xmx2G -Xms2G -XX:+AlwaysPreTouch -XX:-DontCompileHugeMethods -XX:+EnableDynamicAgentLoading --enable-native-access=ALL-UNNAMED -XX:+ExplicitGCInvokesConcurrent -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders

分组和额外参数:

  • 分代 ShenandoahGC(OpenJDK 24 添加实验性功能,OpenJDK 25 将转正):

    -XX:+UseShenandoahGC -XX:+UnlockExperimentalVMOptions -XX:ShenandoahGCMode=generational

  • (非分代) ShenandoahGC(OpenJDK 12 添加,OpenJDK 15 转正,实际支持的 JDKs 见此处):

    -XX:+UseShenandoahGC

  • 分代 ZGC(OpenJDK 21 添加)

    -XX:+UseZGC -XX:+ZGenerational

  • (非分代) ZGC 已于 OpenJDK 24 移除,无法测试。

  • G1GC:

    -XX:+UseG1GC -XX:MaxGCPauseMillis=50

测试方法

  • 每轮测试(一个 GC 方案的一个 空载/单玩家静止/单玩家移动 的测试),使用 JMC 9.1Spark 分析器收集 15 分钟数据

  • 堆内存占用取 JMC 收集到的堆内存最小值

  • MSPT 取自 Spark

  • GC 数据取自 JMC

  • “空载”指无玩家在线时测试,“单玩家静止”指有一个玩家静止在线时测试,“单玩家移动”指一个玩家在一条 300m 长的直线铁路上乘往返时测试

测试数据

如以下图片,MSPT 图中左三列为95%ile,中三列为最高值,右三列为最低值。

关于Java版服务端选择 GC 的小测试/优化推荐(OpenJDK 24)-第1张图片

关于Java版服务端选择 GC 的小测试/优化推荐(OpenJDK 24)-第2张图片

可以显著的看出以下结论:

  • 常规的G1GC 内存占用较低,MSPT / 性能表现最好但是暂停时间长,次数多,容易因为 GC 暂停导致突然卡顿;

  • 其他 GC 方案 GC 暂停时间远远低于 G1GC,不易导致 GC 卡顿但是 MSPT 较高于 G1GC,性能表现略差;

  • 分代 ZGC GC 暂停时间最低, 几乎没有 GC 卡顿,MSPT较高但是堆内存占用最高,对可分配堆内存不足的环境有一定限制;

  • ShenandoahGC 堆内存占用与 G1GC 相近甚至特定情况低于 G1GC,内存占用低且暂停时间第二短,次数最少,GC卡顿低MSPT 也基本处于第二名,性能表现良好整体处于较均衡的水平

  • 分代 ShenandoahGC 不稳定,各项表现均不佳(也许 OpenJDK 25 转正后会好一些)。

  • 事后测试/题外话:对于仅客户端环境(连服务器),分代 ZGC 的帧率(FPS)最高,而 ShenandoahGC 的最低,因此推荐在这种仅客户端环境使用分代 ZGC。

结语

分代 ZGC 并不是万用方案,如特定情境下不分代的 ShenandoahGC 反而综合性能强于 分代的 ZGC(不排除是我给的性能压力太小了)。

如果你想要极致的低暂停时间/无卡顿,且硬件条件好(内存),使用分代 ZGC(JVM参数:-XX:+UseZGC -XX:+ZGenerational);

如果你想要极致的高性能,且硬件条件一般,能受的住卡顿,使用 G1GC(JVM参数:-XX:+UseG1GC);

如果你想兼顾内存、性能和暂停时间,选择 ShenandoahGC(JVM参数:-XX:+UseShenandoahGC)。