前排提示:
[!] 注意,该教程仅面向具有独立服务器的情况,如果你是面板服,就不用看了
[!] 注意,该教程假定你有一块还算不错的CPU,2.XHz的就别看了
[!] 注意,该教程需要一定的Linux知识,并且假定你正在使用Ubuntu,其它系统自己百度
0. 一些废话和声明
Q: 为什么要写这一篇帖子?
A: 这两天在开服,随手翻看了几篇优化帖子,发现都是基于老版本Java的,于是就想写一篇新的。
Q: 我可以转载这篇帖子么?
A: 很抱歉,你不可以这么做,除非你联系我(MCBBS, Twitter, 知乎:@VeroFess)并获得了明确的授权
1. 服务器准备
1.1 升级Java
为啥要升级Java?很简单,更新的Java可以获得更好的性能,但是在高版本里其实你选择哪个Java的影响已经不是很大了。
不论如何,请使用你的方法安装Java 17,最好是Jdk, 完成后, 输入 java --version
, 来检查一下是否安装正常
你应该可以看到类似的输出
$ java --version
openjdk 17.0.3 2022-04-19
OpenJDK Runtime Environment (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)
1.2 开启大页支持
大页 (LargePages) 究竟有什么好处并不是我们今天的重点,一句话来说就是可以让你白捡百分之十几的性能
当然,在动手前,让我们先试一试是不是系统已经支持了这项功能
在控制台中, 输入 java -Xlog:gc+init -XX:+UseLargePages -Xmx1g -version
, 你应该会看到类似的输出
$ java -Xlog:gc+init -XX:+UseLargePages -Xmx1g -version
[0.000s][warning][pagesize] UseLargePages disabled, no large pages configured and available on the system.
[0.023s][info ][gc,init ] Version: 17.0.3+7-Ubuntu-0ubuntu0.20.04.1 (release)
[0.023s][info ][gc,init ] CPUs: 16 total, 16 available
[0.023s][info ][gc,init ] Memory: 31972M
[0.023s][info ][gc,init ] Large Page Support: Disabled
[0.023s][info ][gc,init ] NUMA Support: Disabled
[0.023s][info ][gc,init ] Compressed Oops: Enabled (32-bit)
[0.023s][info ][gc,init ] Heap Region Size: 1M
[0.023s][info ][gc,init ] Heap Min Capacity: 8M
[0.023s][info ][gc,init ] Heap Initial Capacity: 500M
[0.023s][info ][gc,init ] Heap Max Capacity: 1G
[0.023s][info ][gc,init ] Pre-touch: Disabled
[0.023s][info ][gc,init ] Parallel Workers: 13
[0.023s][info ][gc,init ] Concurrent Workers: 3
[0.023s][info ][gc,init ] Concurrent Refinement Workers: 13
[0.023s][info ][gc,init ] Periodic GC: Disabled
openjdk version "17.0.3" 2022-04-19
OpenJDK Runtime Environment (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)
如果你像我一样, 有一行 UseLargePages disabled, no large pages configured and available on the system.
那么就说明当前系统并不支持大页,不过不要急,可以试一下这一行命令 java -Xlog:gc+init -XX:+UseTransparentHugePages -Xmx1g -version
, 你应该会看到类似的输出:
$ java -Xlog:gc+init -XX:+UseTransparentHugePages -Xmx1g -version
[0.024s][info][gc,init] Version: 17.0.3+7-Ubuntu-0ubuntu0.20.04.1 (release)
[0.024s][info][gc,init] CPUs: 16 total, 16 available
[0.024s][info][gc,init] Memory: 31972M
[0.024s][info][gc,init] Large Page Support: Enabled (Transparent)
[0.024s][info][gc,init] NUMA Support: Disabled
[0.024s][info][gc,init] Compressed Oops: Enabled (32-bit)
[0.024s][info][gc,init] Heap Region Size: 1M
[0.024s][info][gc,init] Heap Min Capacity: 8M
[0.024s][info][gc,init] Heap Initial Capacity: 500M
[0.024s][info][gc,init] Heap Max Capacity: 1G
[0.024s][info][gc,init] Pre-touch: Disabled
[0.024s][info][gc,init] Parallel Workers: 13
[0.024s][info][gc,init] Concurrent Workers: 3
[0.024s][info][gc,init] Concurrent Refinement Workers: 13
[0.024s][info][gc,init] Periodic GC: Disabled
openjdk version "17.0.3" 2022-04-19
OpenJDK Runtime Environment (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)
注意到 Large Page Support: Enabled (Transparent)
了么?说明我们的系统是支持透明大页的。
但是如果你依然不支持或者想要追求极致性能,可以去百度搜索当前的系统如何开启大页,这里就不再过多的赘述了。
2. 启动参数
2.1 G1GC vs.ZGC
GC引擎的选择是一个老生常谈的问题了,同其他教程不同,我本人更加推荐ZGC。
理由很简单,现在的CPU动辄八核十六核,但是MC又是一个出了名的吃单核游戏,用其他核心的时间换主核心的无暂停是很划算的事情。
G1GC在设计之初考虑的是吞吐量和占用的均衡,而ZGC则是用更加高昂的CPU代价换取主线程无停顿,这对我们来说简直是刚好。但是并不是每一个人都适合ZGC,在接下来的文章里我会教你怎么样查看自己适不适合它。
2.2 来吧!启动参数!
2.2.1 总览
准备好, 参数要来了,有点长,不过我会分段告诉你他们的作用。
java -server -Xmx${memory}M -XX:+UnlockExperimentalVMOptions
-XX:+UnlockDiagnosticVMOptions -XX:+DisableExplicitGC
-XX:-UseG1GC -XX:+UseZGC -XX:-ZUncommit
-XX:+UseTransparentHugePages -XX:+UseHugeTLBFS
-XX:LargePageSizeInBytes=1g -XX:ReservedCodeCacheSize=512M
-XX:MaxInlineSize=256 -XX:+AlwaysPreTouch -XX:+OmitStackTraceInFastThrow
-XX:+DoEscapeAnalysis -XX:+OptimizeStringConcat -XX:+EliminateLocks
-XX:+UseBiasedLocking -XX:+SegmentedCodeCache -XX:+UseVectorCmov
-XX:+UseSHM -XX:+UseNewLongLShift -XX:+UseFastStosb -jar ${core}.jar
2.2.2 那些不太需要说的部分
-Xmx${memory}M
和 -jar ${core}.jar
这两个参数,着实是不用说…
2.2.3 基础!
-server
这个参数吧,我自己测试确实是有一定的效果的,但是也没其他帖子说的这么夸张,总之加上肯定没错。
而 -XX:+UnlockExperimentalVMOptions
和 -XX:+UnlockDiagnosticVMOptions
这两个参数,纯粹是我懒得去看后面的一大串是不是在他们的影响列表里,所以加上的。
2.2.4 内存管理调整
-XX:+DisableExplicitGC
,-XX:-UseG1GC
,-XX:+UseZGC
,-XX:-ZUncommit
,-XX:+UseTransparentHugePages
,-XX:+UseHugeTLBFS
,-XX:LargePageSizeInBytes=1g
,-XX:ReservedCodeCacheSize=512M
,-XX:MaxInlineSize=256
和-XX:+AlwaysPreTouch
属于内存调整参数。
首先,我们通过-XX:+DisableExplicitGC
来阻止显式的System.gc()
调用,这倒不是说所有的手动调用都是不好的,但是总有些mod或者插件的编写者觉得调用的越频繁越好,但是过于频繁的GC反而会拖慢速度,所以索性就把这个方法干掉,让Java来帮我们管理。
-XX:-UseG1GC
,-XX:+UseZGC
这两个参数用来强制Jvm使用ZGC, 不需要做过多的解释
-XX:-ZUncommit
, -XX:+AlwaysPreTouch
这两个参数呢,要求Jvm总是提前把要用的内存申请好,并且阻止Jvm把临时空出来的内存还给系统,这样会节约不少内存分配的开销,当然,如果你的系统内存不够用,还是去掉这两个参数吧。
-XX:+UseTransparentHugePages
,-XX:+UseHugeTLBFS
,-XX:LargePageSizeInBytes=1g
这三个参数用于启用大页支持,并且允许Jvm使用1G的大页, 如果你的系统支持非透明大页(也就是1.2中的第一个指令没有说不支持),就把-XX:+UseTransparentHugePages
给改成-XX:+UseLargePages
.
-XX:ReservedCodeCacheSize=512M
,-XX:MaxInlineSize=256
这两个参数允许Jvm缓存更多Jit之后的代码,并且允许Jvm把一些比较短的方法做inline优化,是一种用比较小的内存代价来换取性能提升的方法。
2.2.5 其他优化
这一部分主要包含了 -XX:+OmitStackTraceInFastThrow
, -XX:+DoEscapeAnalysis
, -XX:+OptimizeStringConcat
, -XX:+EliminateLocks
这四个参数。
-XX:+OmitStackTraceInFastThrow
这个参数要求Jvm在遇到异常时省略异常栈,因为有一些编写不是很好的mod/插件总是会抛出异常,这也算是一个可能不小的优化。
-XX:+DoEscapeAnalysis
这个参数应该是默认启用的,他要求Jvm进行逃逸分析,并且以分析结果为依据进行其他优化,这里加上只是为了防止某些个例中这玩意由于未知原因被关掉。
-XX:+OptimizeStringConcat
同样, 这个也应该是默认启用的,可以优化字符串拼接的效率。
-XX:+SegmentedCodeCache
这个选项要求Jit编译器把代码拆分为小段,虽然会占用更多的内存,但是会加快运行的速度。
-XX:+UseBiasedLocking
这个选项要求使用偏向锁,由于MC的锁竞争大多发生在主线程,这可以优化一些性能,虽然提示这个东西要移除了,但是能用一天是一天。
-XX:+UseFMA
,-XX:+UseVectorCmov
,-XX:+UseSHM
,-XX:+UseNewLongLShift
,-XX:+UseFastStosb
,这几个选项要求启用一些CPU相关的快速指令,要是你的CPU不支持其中的某一些,就直接删除掉吧。不过一般来说即使不支持也只是单纯的输出一条警告而已。
3. 测试
我们使用 Spark
进行测试,来对比这份参数与另一份网上流传很广的G1GC参数。
在这里,我就使用我自己的服务器进行举例,为了模拟性能捉急的机器,我手动将-Xmx参数设置为10G
为了让服务器有足够高的负载,我将实体限制提升到5000,并且召唤300只凋零来攻击不断被命令方块刷出的牛牛君。
相关的报告可以在 https://spark.lucko.me/srA9dF1wkS (ZGC) 与 https://spark.lucko.me/gzX9cV3xdL (G1GC)看到
TPS 对比:
ZGC : 20.00(1m)/19.50(5m)
G1GC : 19.31(1m)/19.74 (5m)
MSPT 对比:
ZGC : 0.519(最低)/18(平均)/43.2(95%ile)
G1Gc : 0.593(最低)/20(平均)/54.4(95%ile)
CPU 对比:
ZGC : 4.59%(5m)
G1Gc : 5.81%(5m)
内存占用对比:
ZGC : 2 GB/9.6 GB
G1Gc : 3.8 GB/10 GB
虽然不能说完胜吧,但是还是有了不少的提升的,而且随着运行时间变长,使用我的参数性能应当会越来越好(因为有更加激进的Jit方案),而G1则可能更加频繁的STW.
看起来还不错?但是,在之前我们说过,对我好并不等于对你好,适合自己的才是真的好,在内存非常小的情况下,ZGC反而会占用非常高的CPU,如果你发现你的mspt飙升并且出现了GC分配停顿的状况,则应该考虑换回G1GC。