NameServ启动脚本剖析
概览
- mqnamesrv命令
- mqnamesrv 脚本
runserver.sh 脚本
- 大于等于JDK9的启动参数
- 等于JDK8的启动参数
mqnamesrv 启动命令
这里间接摘录了官网文档:
Start NameServer
### Start Name Server first$ nohup sh mqnamesrv &
### Then verify that the Name Server starts successfully$ tail -f ~/logs/rocketmqlogs/namesrv.logThe Name Server boot success...
mqnamesrv 脚本
#!/bin/sh# 从环境变量当中获取RocketMq环境变量地址if [ -z "$ROCKETMQ_HOME" ] ; then ## resolve links - $0 may be a link to maven's home ## 解决链接问题 - $0 可能是maven的主页链接 # PS:$0 是脚本的命令自身 PRG="$0" # need this for relative symlinks # 须要相干链接 while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done # 暂存以后的执行门路 saveddir=`pwd` ROCKETMQ_HOME=`dirname "$PRG"`/.. # make it fully qualified # 拼接获取RocketMQ绝对路径 ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd` # 跳转到以后暂存的命令执行门路 cd "$saveddir"fiexport ROCKETMQ_HOME# 要害: 执行runserver.sh脚本,携带logback的日志xml配置,以及传递JVM的启动main办法的入口类绝对路径sh ${ROCKETMQ_HOME}/bin/runserver.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.namesrv.logback.xml org.apache.rocketmq.namesrv.NamesrvStartup $@
最开始的mqnamesrv.sh
脚本获取环境变量的局部看不懂其实没啥影响,大略有个印象即可,当然能够截取局部的命令到Linux运行测试一下就明确了,比方筹备环境变量等等,最初一句话比拟要害。
留神最初的两个字符$@
,这两个字符的作用如下:
$@ :示意所有脚本参数的内容。
$# :示意返回所有脚本参数的个数。
再次强调后面的一大坨获取环境变量看不懂没关系,看懂外围的执行脚本即可。
runserver.sh 脚本
runserver.sh 的脚本内容如下:
#!/bin/sh#===========================================================================================# Java Environment Setting#===========================================================================================error_exit (){ echo "ERROR: $1 !!" exit 1}find_java_home(){ # uname 是获取Linux内核参数的指令,不带任何参数获取以后操作系统的类型,比方Linux就是“Linux”的文本 case "`uname`" in Darwin) JAVA_HOME=$(/usr/libexec/java_home) ;; *) # 能够简略认为获取到javac命令的绝对路径,而后执行两次cd..操作,以此作为JDK的门路 # 比方 /opt/jdk/bin/javac dirname 两次之后就是 /opt/jdk JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac)))) ;; esac}# 调用函数find_java_home# 读取JAVA命令的执行地址[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java[ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!"# export 导出的长期环境变量,只实用以后SHELL连贯# JAVA 命令的执行地址,设置为环境变量export JAVA_HOMEexport JAVA="$JAVA_HOME/bin/java"# $0 代表以后的申请传递的第一个参数,依据上一个脚本能够晓得是:${ROCKETMQ_HOME}/bin/runserver.shexport BASE_DIR=$(dirname $0)/..# 因为须要启动JVM过程,须要从ROCKETMQ_HOME的conf和lib门路通知JDK找依赖包以及相干的配置文件export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH}#===========================================================================================# JVM Configuration#===========================================================================================# The RAMDisk initializing size in MB on Darwin OS for gc-log# 在 Darwin OS 上为 gc-log 初始化 RAMDisk 的大小(以 MB 为单位)DIR_SIZE_IN_MB=600choose_gc_log_directory(){ # Darwin 操作系统须要非凡解决,疏忽 case "`uname`" in Darwin) if [ ! -d "/Volumes/RAMDisk" ]; then # create ram disk on Darwin systems as gc-log directory DEV=`hdiutil attach -nomount ram://$((2 * 1024 * DIR_SIZE_IN_MB))` > /dev/null diskutil eraseVolume HFS+ RAMDisk ${DEV} > /dev/null echo "Create RAMDisk /Volumes/RAMDisk for gc logging on Darwin OS." fi GC_LOG_DIR="/Volumes/RAMDisk" ;; *) # # check if /dev/shm exists on other systems # 查看 /dev/shm 是否存在于其余零碎上 # What Is /dev/shm And Its Practical Usage # https://www.cyberciti.biz/tips/what-is-devshm-and-its-practical-usage.html if [ -d "/dev/shm" ]; then GC_LOG_DIR="/dev/shm" else GC_LOG_DIR=${BASE_DIR} fi ;; esac}choose_gc_options(){ # 依据JDK的版本抉择适合的GC参数,RocketMq最低须要JDK8,所以如果是1结尾就是10以及之后的JDK版本 # Example of JAVA_MAJOR_VERSION value: '1', '9', '10', '11', ... # '1' means releases befor Java 9 JAVA_MAJOR_VERSION=$("$JAVA" -version 2>&1 | sed -r -n 's/.* version "([0-9]*).*$/\1/p') if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "9" ] ; then # 大于JDK 9 版本之后的参数 # 堆内存(初始堆内存)为 4 g,新生代 2g,其余空间为 2g。元空间初始化128m,最大的扩容元空间为320mb JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" # g1收集器在jdk11失去并行Full GC能力,而zgc在jdk11版本处于试验状态,这里抉择了比拟稳当的 CMS 老年代垃圾回收器 # UseCMSCompactAtFullCollection:CMS垃圾在进行了Full GC时,对老年代进行压缩整顿,解决掉内存碎片 # CMSParallelRemarkEnabled 应用CMS老年代收集器 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" else 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" fi}choose_gc_log_directorychoose_gc_optionsJAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"#JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n"JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}"$JAVA ${JAVA_OPT} $@
runserver.sh
脚本的内容,内容比拟多,这里拆分解说。
前置筹备工作
首先是无关环境变量的配置获取以及查找JAVA_HOME。
#!/bin/sh#===========================================================================================# Java Environment Setting#===========================================================================================error_exit (){ echo "ERROR: $1 !!" exit 1}find_java_home(){ # uname 是获取Linux内核参数的指令,不带任何参数获取以后操作系统的类型,比方Linux就是“Linux”的文本 case "`uname`" in Darwin) JAVA_HOME=$(/usr/libexec/java_home) ;; *) # 能够简略认为获取到javac命令的绝对路径,而后执行两次cd..操作,以此作为JDK的门路 # 比方 /opt/jdk/bin/javac dirname 两次之后就是 /opt/jdk JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac)))) ;; esac}# 调用函数find_java_home# 读取JAVA命令的执行地址[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java[ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!"# export 导出的长期环境变量,只实用以后SHELL连贯# JAVA 命令的执行地址,设置为环境变量export JAVA_HOMEexport JAVA="$JAVA_HOME/bin/java"# $0 代表以后的申请传递的第一个参数,依据上一个脚本能够晓得是:${ROCKETMQ_HOME}/bin/runserver.shexport BASE_DIR=$(dirname $0)/..# 因为须要启动JVM过程,须要从ROCKETMQ_HOME的conf和lib门路通知JDK找依赖包以及相干的配置文件export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH}
错误处理
首先是结尾局部,如果出现异常就打印谬误参数。在SH脚本文件中,$1
代表了跟在脚本前面的第一个参数,比方./script.sh filename1 dir1
,则$1 = filename1
:
error_exit (){ // $1 通常是调用函数传入的第一个参数,比方下文的:Please set the JAVA_HOME variable in your environment, We need java(x64)! echo "ERROR: $1 !!" exit 1}
查找JDK Home
接着是查找 JAVA_HOME 的地位:
find_java_home(){ # uname 是获取Linux内核参数的指令,不带任何参数获取以后操作系统的类型,比方Linux就是“Linux”的文本 case "`uname`" in Darwin) JAVA_HOME=$(/usr/libexec/java_home) ;; *) # 能够简略认为获取到javac命令的绝对路径,而后执行两次 cd.. 操作,以此作为JDK的门路 # 比方 /opt/jdk/bin/javac dirname 两次之后就是 /opt/jdk JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac)))) ;; esac}
uname 是获取Linux内核参数的指令,不带任何参数获取以后操作系统的类型,比方Linux就是“Linux”的文本:
xander@xander:~$ uname Linux
这里通过uname 查找内核,如果是 darwin 的操作系统获取门路要非凡一些。而其余的形式则是$(dirname $(dirname $(readlink -f $(which javac))))
层层查找:
[zxd@localhost ~]$ echo $(dirname $(dirname $(readlink -f $(which javac))));/opt/jdk8
这些能够简略认为获取到javac
命令的绝对路径,而后执行两次cd..
操作,以此作为JDK的门路。最简略的验证办法是放到Linux上执行一下:
[zxd@localhost ~]$ echo $(readlink -f $(which javac))/opt/jdk8/bin/javac
[zxd@localhost ~]$ echo $(dirname $(dirname $(readlink -f $(which javac))));/opt/jdk8
取JAVA命令的执行地址
这里比较简单,最初一句调用了error_exit函数,对应了第一个参数就是要打印的值。
# 读取JAVA命令的执行地址[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java[ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)!"
# export 导出的长期环境变量,只实用以后SHELL连贯# 取JAVA 命令的执行地址,设置为环境变量export JAVA_HOMEexport JAVA="$JAVA_HOME/bin/java"# $0 通常代表脚本名称自身,这里获取进去的后果是:${ROCKETMQ_HOME}export BASE_DIR=$(dirname $0)/..# 因为须要启动JVM过程,须要把ROCKETMQ_HOME的conf和lib门路通知JDK找依赖包以及相干的配置文件export CLASSPATH=.:${BASE_DIR}/conf:${BASE_DIR}/lib/*:${CLASSPATH}
下面须要留神的点:
- export 导出的长期环境变量,只实用以后SHELL连贯。
$0
代表以后j脚本名称自身,../
和dirname
联合相似../../
成果,$(dirname $0)/..
为Rocketmq的装置目录地址。- 须要把
ROCKETMQ_HOME
的conf
和lib
门路通知JDK找依赖包以及相干的配置文件。
JAVA="$JAVA_HOME/bin/java"BASE_DIR=${ROCKETMQ_HOME}/binCLASSPATH=.:${ROCKETMQ_HOME}/bin/conf:${BASE_DIR}/lib/*:${CLASSPATH}
JVM 配置局部
之后往下的局部是JVM的配置局部,这部分咱们拆分成两个局部来讲,最要害的是JVM参数配置局部,最开始是获取JVM 的GC_LOG地址,这里用uname
辨认操作系统,
# 在 Darwin OS 上为 gc-log 初始化 RAMDisk 的大小(以 MB 为单位)DIR_SIZE_IN_MB=600choose_gc_log_directory(){ # Darwin 操作系统须要非凡解决,疏忽 case "`uname`" in Darwin) if [ ! -d "/Volumes/RAMDisk" ]; then # create ram disk on Darwin systems as gc-log directory DEV=`hdiutil attach -nomount ram://$((2 * 1024 * DIR_SIZE_IN_MB))` > /dev/null diskutil eraseVolume HFS+ RAMDisk ${DEV} > /dev/null echo "Create RAMDisk /Volumes/RAMDisk for gc logging on Darwin OS." fi GC_LOG_DIR="/Volumes/RAMDisk" ;; *) # # check if /dev/shm exists on other systems # 查看 /dev/shm 是否存在于其余零碎上 # What Is /dev/shm And Its Practical Usage # https://www.cyberciti.biz/tips/what-is-devshm-and-its-practical-usage.html if [ -d "/dev/shm" ]; then GC_LOG_DIR="/dev/shm" else GC_LOG_DIR=${BASE_DIR} fi ;; esac}
在Linux零碎中,会查看 /dev/shm
是否存在零碎上,如果是就把GC_LOG挂载到/dev/shm
,否则就以后RocketMQ的装置目录GC_LOG_DIR=${BASE_DIR}
。
这里补充一下/dev/shm
是什么?在Linux中,这块空间起很大的作用,因为他不是硬盘而是一块内存空间,默认为VM的一半大小,应用df -h
命令能够看到:
xander@xander:~$ df -hFilesystem Size Used Avail Use% Mounted ontmpfs 389M 1.7M 388M 1% /run/dev/mapper/ubuntu--vg-ubuntu--lv 14G 7.3G 5.8G 56% /tmpfs 1.9G 0 1.9G 0% /dev/shmtmpfs 5.0M 0 5.0M 0% /run/lock/dev/sda2 2.0G 247M 1.6G 14% /boottmpfs 389M 4.0K 389M 1% /run/user/1000
RocketMq为什么要应用这一块空间?
tmpfs有以下劣势:
- 动静文件系统的大小。
- tmpfs 文件系统会齐全驻留在 RAM 中,领有近似内存的读写速度。
而毛病仅仅是 tmpfs 数据在重新启动之后不会保留,能够做一些脚本备份的操作。
tmpfs 和 /dev/shm 解释
这块空间的专业名词叫做:tmpfs(虚拟内存零碎),tmpfs最大的特点就是它的存储空间在VM(virtual memory),VM是由linux内核外面的vm子系统治理的。
VM中又划分为RM (real memory) 和 swap,RM 就是VM理论可用的内存空间,而swap是用于辅助VM在RM不够的时候就义硬盘作为内存空间应用,同样RM还会把不罕用的数据放到Swap。
tmpfps = RM (real memory) + swap。
tmpfs默认的大小是RM的一半,如果你的物理内存是1024M,那么tmpfs默认的大小就是512M。能够通过mount命令扩充这块空间大小。
tmpfps的存在意义是能够动静的扩容和放大,并且只有不应用这块空间它自身没有任何的内存占用(0字节)(零老本还好用),而一旦应用则能够把读写停留在内存保证数据霎时实现,然而代价是这块空间不具备记忆性能,重启之后不会被保留。
参考资料:
- What Is /dev/shm And Its Practical Usage
JVM外围参数
外围参数的局部就是JVM的启动参数配置,也是脚本最为外围局部。
大于等于JDK9的启动参数
对应了 gc_options 的上半局部,首先判断JDK版本大于8之后的状况:
if [ -z "$JAVA_MAJOR_VERSION" ] || [ "$JAVA_MAJOR_VERSION" -lt "9" ] ; then # 大于JDK 9 版本之后的参数 # 堆内存(初始堆内存)为 4 g,新生代 2g,其余空间为 2g。元空间初始化128m,最大的扩容元空间为320mb JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" # g1收集器在jdk11失去并行Full GC能力,而zgc在jdk11版本处于试验状态,这里抉择了比拟稳当的 CMS 老年代垃圾回收器 # UseCMSCompactAtFullCollection:CMS垃圾在进行了Full GC时,对老年代进行压缩整顿,解决掉内存碎片 # CMSParallelRemarkEnabled 应用CMS老年代收集器 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"
首先是JVM堆大小和元空间大小调配:
JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
-server
启用-server
时新生代默认采纳并行收集,其余状况下,默认不启用。-server
策略为:新生代应用并行革除,老年代应用单线程Mark-Sweep-Compact
的垃圾收集器。
堆大小设置
设置JVM的堆大小,堆内存(初始堆内存)为 4g,新生代 2g,其余空间为 2g,元空间初始化128M,最大的扩容元空间为320M。
如果是集体机器配置比拟低,倡议把这几个值调小一些。
上面的参数比拟要害,RocketMq在JDK8之后没有抉择G1而是应用了CMS,也是集体不太了解的点,因为G1收集器在jdk11失去并行Full GC能力,而ZGC在JDK11版本处于试验状态。
集体了解是思考到后续的版本垃圾收集器变动比拟大,这里抉择了G1之前比拟稳当的 CMS 老年代垃圾回收器。
# UseCMSCompactAtFullCollection:CMS垃圾在进行了Full GC时,对老年代进行压缩整顿,解决掉内存碎片# CMSParallelRemarkEnabled 应用CMS老年代收集器JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC"
UseCMSCompactAtFullCollection
CMS垃圾在进行了Full GC时,对老年代进行压缩整顿,解决掉内存碎片
CMSParallelRemarkEnabled
老年代收集器指定为CMS,在进行了Full GC时对老年代进行压缩整顿,解决掉内存碎片。
-XX:+UseCMSCompactAtFullCollection
CMS垃圾收集器
这里补充一下CMS垃圾收集器的知识点:
CMS是基于标记革除算法实现的多线程老年代垃圾回收器。CMS为响应工夫优先的垃圾回收器,适宜于应用服务器,如网络游戏,电商等和电信畛域的利用。Rocketmq自身就是诞生于电商平台应用CMS是比拟稳当的。
CMS垃圾收集器特点
- FUll GC大部分工夫和应用程序并行,代价是减少CPU开销。
- 并发FULL GC短暂进展。
- 用户线程和回收线程并行。
垃圾回收算法:标记革除算法
之后是对应的CMS罕用优化参数。
-XX:+UseCMSCompactAtFullCollection
设置此参数之后,CMS垃圾在进行了Full GC时,对老年代进行压缩整顿,解决掉内存碎片。RocketMq 的脚本也开启了每次Full Gc之后进行碎片整顿。
-XX:CMSFullGCsBeforeCompaction=1
FUll GC 之后对老年代进行压缩整顿,解决掉内存碎片。RocketMq 的默认应答策略是踊跃的进行内存碎片整顿,放大老年代的大小,因为RocketMq须要的是高响应工夫。
CMSInitiatingOccupancyFraction=70
CMSInitiatingOccupancyFraction=70 示意当老年代达到70%时,触发CMS垃圾回收。
- 计算老年代最大使用率(\_initiating\_occupancy)
- 大于等于0则间接取百分号
- 小于0则依据公式来计算
如果应用默认值,则老年代触发回收的比例是动静的,不同的JDK版本可能会有不同的默认值,
((100 - MinHeapFreeRatio) + (double) ( CMSTriggerRatio * MinHeapFreeRatio) / 100.0) / 100.0
-XX:SoftRefLRUPolicyMSPerMB=0
官网解释是:Soft reference 在虚拟机中比在客户集中存活的更长一些。
先说一下论断,集体认为这个参数设置为0是不应该的,至多须要设置个1000或者500(半秒)的缓冲,为什么呢?
咱们假如咱们不小心把这个值设置为0有什么结果呢?
如果-XX:SoftRefLRUPolicyMSPerMB=0
,会导致下面clock公式的计算结果为0。
这个后果为0,就是软饮用被频繁回收导致触发频繁的GC,JVM发现每次反射调配的对象马上就会被回收掉,而后接着又会通过代理生成代理对象,导致每次soft软援用的对象一旦调配就会马上被回收.
论断就是这个值为0,反射机制导致动静代理类一直的被新增,然而这部分对象又被马上回收掉,导致办法区的垃圾对象越来越多,会导致很多垃圾无奈齐全回收。
为什么RocketMq默认要把-XX:SoftRefLRUPolicyMSPerMB=0
值设置为0?
咱们接着剖析,-XX:SoftRefLRUPolicyMSPerMB=0
,然而应用的是服务器模式(-server),在server模式下,会用 -Xmx 参数获取闲暇空间大小。
闲暇的空间越大,软援用就会活的越久,如果设置的值过大,很可能因为框架反射类创立的软援用过多然而因为存在闲暇工夫计算又没法回收的状况。
把这个值设置为0,基本上就是说不让反射产生的一些meta对象在GC之后回收不掉,间接通过1次GC就给他摆平了。然而集体角度来看未免过于极其,集体认为设置为0是不适合的。
参考案例:
这里在网上找到电子表格缓存业务因为设置 -XX:SoftRefLRUPolicyMSPerMB=0 导致的问题例子。
当JVM参数中配置了 -XX:SoftRefLRUPolicyMSPerMB=0 参数,这个参数是管制SoftReference缓存工夫,而咱们的电子表格的缓存都是存储SoftReference里边的,当设置了这个参数设置为0的时候,任意操作,只有是触发了gc,这时候就会清空了电子表格缓存,导致即便在内存足够的状况下,缓存也不失效了。
革除频率能够用命令行参数 -XX:SoftRefLRUPolicyMSPerMB=<N>
来管制,能够指定每兆堆闲暇空间的 Soft reference 放弃存活(一旦它饮用后不可达了)的毫秒数,这意味着每兆堆中的闲暇空间中的 soft reference 会(在最初一个强援用被回收之后)存活1秒钟。
当然并不是什么时候 -XX:SoftRefLRUPolicyMSPerMB=0都是错的,因为 soft reference 只会在垃圾回收时才会被革除,而垃圾回收并不总在产生。零碎默认为一秒,如果感觉客户端不须要任何保留,改为 -XX:SoftRefLRUPolicyMSPerMB=0
能够及时清理洁净数据。
RocketMq的做法集体了解为想要让垃圾回收尽可能的回收洁净对象,因为RocketMq并不是非常吃JVM堆内存,更多的是须要页缓存,况且NameServ自身比拟轻量级。
还有一个起因是软援用这货色能不必就尽量不必,危险比拟大。
-XX:+CMSClassUnloadingEnabled
老年代启用CMS,但默认是不会回收永恒代(Perm)的。此处启用对Perm区启用类回收,避免Perm区内存垃圾对象堆满(须要与+CMSPermGenSweepingEnabled同时启用)。
-XX:+CMSPermGenSweepingEnabled:
同上,为了防止Perm区满引起的Full GC,开启并发收集器回收Perm区选项。
然而实际上这篇帖子上指出 https://stackoverflow.com/questions/3717937/cmspermgensweepingenabled-vs-cmsclassunloadingenabled
-XX:+CMSClassUnloadEnabled
和 -XX:+CMSPermGenSweepingEnabled
在 Java 1.7 中不可用,然而选项-XX:+CMSClassUnloadEnabled
对于Java 1.7依然无效。换句话说在JDK1.7之后被倡议应用-XX:+CMSClassUnloadEnabled
。
JVM1.7之前是什么状况?为什么会须要与+CMSPermGenSweepingEnabled同时启用
上面这篇文章有评论进行了解释:
用户对问题“CMSPermGenSweepingEnabled vs CMSClassUnloadingEnabled”的答复 - 问答 - 腾讯云开发者社区-腾讯云 (tencent.com)")
1.6 JVM的CMSPermGenSweepingEnabled
参数做的惟一事件就是打印消息,它的解决形式与1.5不同。要使CMSClassUnloadingEnabled
失效,还必须设置UseConcMarkSweepGC
。
-XX:SurvivorRatio=8
CMS 用的是标记革除的算法,应用CMS还是传统的新生代和老年代的分代概念,这里RocketMq用的是默认的分代分区策略,给了新生代更多的应用空间。它定义了新生代中Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10.
-UseParNewGC
-XX:+UseParNewGC
设置年老代为多线程收集。可与CMS收集同时应用,ParNew 在Serial根底上实现的多线程收集器。
日志参数配置
总得来说 RocketMq 在JDK8当前的版本应用了老牌的-XX:+UseConcMarkSweepGC
CMS垃圾收集器 和 -XX:+UseParNewGC
CMS垃圾 垃圾收集器。
-verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
-verbose:gc
和-XX:+PrintGCDetails
在官网文档中有阐明两者性能一样,都用于垃圾收集时的信息打印。
然而也有不同点:
-verbose:gc 是 稳固版本,参见:http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html
-XX:+PrintGC 是非稳固版本。
-XX:+PrintGCDateStamps,日志中增加工夫标记(日志每行结尾显示自从JVM启动以来的工夫,单位为秒)
留神-XX:+PrintGCDateStamps
打印GC产生时的工夫戳,搭配 -XX:+PrintGCDetails
应用,不能够独立应用
日志输入示例:
2014-01-03T12:08:38.102-0100: [GC 66048K->53077K(251392K), 0,0959470 secs]2014-01-03T12:08:38.239-0100: [GC 119125K->114661K(317440K), 0,1421720 secs]
-XX:+UseGCLogFileRotation
、-XX:NumberOfGCLogFiles=5
、 -XX:GCLogFileSize=30m
。UseGCLogFileRotation 会依据前面两个参数的设置一直的轮询替换GC日志,这里最多保留了150M的GC日志,后续再进行写入就会从第一个文件开始替换。
150M日志足够及时处理大部分问题,并且不会呈现历史日志长期驻留磁盘的问题。然而这个参数须要审慎设置,如果设置过小容易导致要害GC 日志失落。
等于JDK8的启动参数
应该说是比拟要害的版本,上面的参数对应JDK8的版本,很显著都是围绕G1垃圾收集器做的优化:
else 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"
第一行
比照发现JDK9版本没啥区别:堆内存(初始堆内存)为 4g,新生代 2g,其余空间为 2g。元空间初始化128m,最大的扩容元空间为320mb。
第二行
垃圾回收器的要害配置:
-XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0
-XX:+UseG1GC
应用G1垃圾收集器。须要留神JDK8的G1垃圾收集器是“残血”版本。
-XX:G1HeapRegionSize
一个Region的大小能够通过参数-XX:G1HeapRegionSize
设定,取值范畴从1M到32M,且是2的指数。如果不设定,那么G1会依据Heap大小主动决定。
要害局部为region_size = 1 << region_size_log
,左移的操作也就是2的倍数的概念。最初的参数判断会让region最大为32M,最小为1M,不容许超过这个范畴。
-XX:G1ReservePercent
`-XX:G1ReservePercent G1会保留一部分堆内存用来避免调配不了的状况,默认是10。
官网对于这个参数的介绍如下:
-XX:G1ReservePercent=10
Sets the percentage of reserve memory to keep free so as to reduce the risk of to-space overflows. The default is 10 percent. When you increase or decrease the percentage, make sure to adjust the total Java heap by the same amount. This setting is not available in Java HotSpot VM, build
设置保留内存的百分比,以缩小到空间溢出的危险。默认是10%。当你减少或缩小这个百分比时,请确保预留足够的空间并且调整Java堆大小。留神这个设置在Java HotSpot VM中是不可用的。
扩充这个数值能够保障在进行GC 的时候提供更多堆内存保障存活空间寄存降职老年代的Region.
G1为调配担保预留的空间比例:通过-XX:G1ReservePercent
指定,默认10%。也就是老年代会预留10%的空间来给新生代的对象降职,如果常常产生新生代降职失败而导致 Full GC,那么能够适当调高此阈值。然而调高此值同时也意味着升高了老年代的理论可用空间。
-XX:InitiatingHeapOccupancyPercent=30
触发全局并发标记的老年代应用占比,默认值45%。
默认值45%的含意是也就是老年代占堆的比例超过45%。如果Mixed GC周期完结后老年代使用率还是超过45%,那么会再次触发全局并发标记过程,这样就会导致频繁的老年代GC,影响利用吞吐量。
当然调大这个值的代价是可能导致年老代谨升失败而导致FULL GC。RocketMq应用30的设置是让老年代提前的触发GC并且回收垃圾。
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:SoftRefLRUPolicyMSPerMB=N
这个参数在是JVM零碎参数和垃圾收集器无关。
-XX:SoftRefLRUPolicyMSPerMB
参数,能够指定每兆堆闲暇空间的软援用的存活工夫,默认值是1000,也就是1秒。能够调低这个参数来触发更早的回收软援用。如果调高的话会有更多的存活数据,可能在GC后堆占用空间比会减少。 对于软援用,还是倡议尽量少用,会减少存活数据量,减少GC的解决工夫。
日志参数配置
配置和之前的版本是一样的,这里间接疏忽了。
${JAVA_OPT} -Xlog:gc*:file=${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log:time,tags:filecount=5,filesize=30M"
总结
整个NameServ的启动脚本不算太简单,这里简略演绎一下比拟重要的点:
- JDK9之后的版本摈弃G1,抉择传统的CMS+ParNew经典组合,不过NameServ 大部分时候干的不是什么轻活,从保障高可用的角度是没啥问题的。
- JDK8应用的是G1垃圾收集器,并且参数都是尽可能的给新生代预留空间。
- NameServ 新生代的压力会比拟大,整体思路是尽可能的缩小垃圾,通过踊跃的GC保障垃圾尽可能被回收。
写在最初
集体认为这种学习形式一举多得,还能够看到不少Shell脚本的应用技巧,挺不错的。