GC垃圾回收器是JVM中最标志性的一个功能特性。而GC的性能极大程度决定了整个JAVA程序执行的性能。因此,对整个JVM调优或许难度太大,但是对GC进行调优,是每个JAVA程序员都应该掌握的技能。
我们先来回顾一下JDK17中有哪些参数可以调。
关于 JVM 的参数,JVM 提供了三类参数。
一类是标准参数。以-开头,所有 HotSpot 都支持。例如java -version。这类参数可以使用java -help 或者java -? 全部打印出来
以下是一些常用的标准参数:
--list_modules : 查看当前JAVA进程中的模块
--show-module-resolution: 查看当前JAVA进程中各个模块的依赖关系
-verbose:class : 显示类加载的信息
-verbose:gc : 显示GC事件
二类是非标准参数。以-X 开头,是特定 HotSpot版本支持的指令。例如java -Xms200M -Xmx200M。这类指令可以用java -X 全部打印出来。这一类参数一般都还是比较稳定,除非有重大的版本升级,一般不会有太大的变化。
例如: -Xint 表示当前JAVA进程采用解释执行。-Xcomp表示当前JAVA进程采用编译执行。-Xmixed则表示采用两种执行引擎混合的方式执行。
-Xbatch 禁用后台编译。默认情况下,JVM将该方法作为后台任务进行编译,在解释器模式下运行该方法,直到后台编译完成。-Xbatch标志禁用后台编译,以便所有方法的编译都作为前台任务进行,直到完成。此选项等效于-XX:-BackgroundCompilation。
最后一类,不稳定参数。这也是 JVM调优的噩梦。以-XX 开头,这些参数是跟特定HotSpot版本对应的,很有可能换个版本就没有了。JDK中的以下几个指令可以帮助开发者了解 这一类不稳定参数。
java -XX:+PrintFlagsFinal:所有最终生效的不稳定指令。
java -XX:+PrintFlagsInitial:默认的不稳定指令
java -XX:+PrintCommandLineFlags:当前命令的不稳定指令
小题目:JDK17默认用的是哪种垃圾回收器?
留个小的课外题:观察下你在IDEA里启动一个项目,具体的启动指令是什么样的?自行尝试下不用IDEA,直接用java指令启动下自己的一个小项目。
接下来,我们可以通过在java指令后加入相关的参数进行定制。例如对于数字型的参数,可以直接在java指令后指定。而对于boolean型的参数,可以通过在参数前面添加一个加号表示设置为true,添加一个减号则表示设置为false。例如:
java -XX:ActiveProcessorCount=1 -XX:+AggressiveHeap -XX:+PrintFlagsFinal -version
在真实项目当中,要如何合理的定制GC相关的运行参数呢?
关于这个问题,其实没有正确答案。要是有绝对正确的答案,那么JDK早就将这些参数的默认值调整为这个正确答案了,我们也就不需要学习了。 而我们想要学习合理定制GC的参数,唯一的方法也就是多练,多尝试。
但是,绝大部分朋友都会遇到的一个共同的困惑。真实项目的线上服务器管控是非常严格的,大概率你是接触不到的。接触不到服务器意味着你没有服务优化的经验。而没有经验,就更不会让你去接触服务器了。那怎么打破这个死局呢?我的建议是跟优秀的开源软件学习。因为这是所有人都能够接触到的,质量最靠谱的java程序了。
例如,在RocketMQ中,有一个核心服务NameServer,这也是一个Java编写的应用程序。他的运行脚本是这样的:
现在,你可能对RocketMQ这个中间件还完全不了解,没关系,这不是现在的关键,你只要知道他就是一个和我们自己写的Java程序一样的应用程序就可以了。
从这个脚本当中你可以看到,RocketMQ在运行这个java程序时,定制了非常多的参数。而这其中有很多参数,你应该是已经有过接触的了。
这段脚本当中,你唯一感到陌生的,应该就是其中那个JAVA_MAJOR_VERSION指令了。其实那个指令就是打印出当前环境的JDK版本。
JAVA -version 2>&1 | awk -F '"' '/version/ {print $2}' | awk -F '.' '{print $1}'
17
这样你就能看到,在choose_gc_options函数中,其实就是根据JDK版本不同,定制不同的GC参数。而这,其实就是我们需要重点学习的,如何在真实环境当中选择合适的参数组合了。
接下来,我们也就以RocketMQ的这一个脚本作为示例,来整体回顾一下如何定制一个JAVA程序合理的运行参数。
我们先来重点关注choose_gc_options函数中的调优参数。
对于9以前的版本,RocketMQ选择的GC参数是这样的
JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC"
JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
而对于9以后的版本,RocketMQ选择的GC参数是这样的:
JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0"
JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log:time,tags:filecount=5,filesize=30M"
JVM的课程学到现在,这些参数你应该是会有一点点感觉的吧。发现他们的共同点了没有?其实不管在哪个JDK版本下,RocketMQ的这个脚本中,定制GC参数大概都分为这三个步骤。
调整内存布局
选择具体的GC算法,并定制GC算法的部分参数
打印GC日志
其实,如果你对RocketMQ有一定的了解,那么你会发现,在RocketMQ中,对另外一个关键服务Broker,其实也是按照这样的思路来定制参数的。对于NameServer和Broker这两种服务,他们的业务场景是不一样的,但是RocketMQ都是按照相同的思路进行参数调优的。而这种思路,其实也是我们学来用到我们自己项目当中的。当然,我们要学习的是这种参数调优的思路,而不是复制粘贴。
再次强调,对于RocketMQ的Broker和NameServer不了解没有关系,这不是今天的重点。
我们今天要讨论的并不是RocketMQ这个特定的业务场景,而是要开始逐渐能够跟着开源软件