本篇教程由作者设定使用 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.1 和 Spark 分析器收集 15 分钟数据
堆内存占用取 JMC 收集到的堆内存最小值
MSPT 取自 Spark
GC 数据取自 JMC
“空载”指无玩家在线时测试,“单玩家静止”指有一个玩家静止在线时测试,“单玩家移动”指一个玩家在一条 300m 长的直线铁路上乘往返时测试
测试数据
如以下图片,MSPT 图中左三列为95%ile,中三列为最高值,右三列为最低值。
可以显著的看出以下结论:
常规的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)。