最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

【RocketMq】NameServ啟動腳本分析(Ver4.9.4)

2023-02-20 07:00 作者:懶時小窩  | 我要投稿

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.log
The?Name?Server?boot?success...

mqnamesrv 腳本

#!/bin/sh
#?從環(huán)境變量當中獲取RocketMq環(huán)境變量地址
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
??#?暫存當前的執(zhí)行路徑
??saveddir=`pwd`

??ROCKETMQ_HOME=`dirname?"$PRG"`/..

??#?make?it?fully?qualified
??#?拼接獲取RocketMQ絕對路徑
??ROCKETMQ_HOME=`cd?"$ROCKETMQ_HOME"?&&?pwd`
??#?跳轉到當前暫存的命令執(zhí)行路徑
??cd?"$saveddir"
fi

export?ROCKETMQ_HOME

#?關鍵:?執(zhí)行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 腳本獲取環(huán)境變量的部分看不懂其實沒啥影響,大略有個印象即可,當然可以截取部分的命令到Linux運行測試一下就明白了,比如準備環(huán)境變量等等,最后一句話比較關鍵。

注意最后的兩個字符$@,這兩個字符的作用如下:

$@ :表示所有腳本參數的內容。

$# :表示返回所有腳本參數的個數。

再次強調前面的一大坨獲取環(huán)境變量看不懂沒關系,看懂核心的執(zhí)行腳本即可。

runserver.sh 腳本

runserver.sh 的腳本內容如下:

#!/bin/sh

#===========================================================================================
#?Java?Environment?Setting
#===========================================================================================
error_exit?()
{
????echo?"ERROR:?$1?!!"
????exit?1
}

find_java_home()
{
????#?uname?是獲取Linux內核參數的指令,不帶任何參數獲取當前操作系統(tǒng)的類型,比如Linux就是“Linux”的文本
????case?"`uname`"?in
????????Darwin)
????????????JAVA_HOME=$(/usr/libexec/java_home)
????????;;
????????*)
????????#?可以簡單認為獲取到javac命令的絕對路徑,然后執(zhí)行兩次cd..操作,以此作為JDK的路徑
????????#?比如?/opt/jdk/bin/javac?dirname?兩次之后就是?/opt/jdk
????????????JAVA_HOME=$(dirname?$(dirname?$(readlink?-f?$(which?javac))))
????????;;
????esac
}

#?調用函數
find_java_home

#?讀取JAVA命令的執(zhí)行地址
[?!?-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?導出的臨時環(huán)境變量,只適用當前SHELL連接
#?JAVA?命令的執(zhí)行地址,設置為環(huán)境變量
export?JAVA_HOME
export?JAVA="$JAVA_HOME/bin/java"
#?$0?代表當前的請求傳遞的第一個參數,根據上一個腳本可以知道是:${ROCKETMQ_HOME}/bin/runserver.sh
export?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=600

choose_gc_log_directory()
{
????#?Darwin?操作系統(tǒng)需要特殊處理,忽略
????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?是否存在于其他系統(tǒng)上
????????????#?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版本處于實驗狀態(tài),這里選擇了比較穩(wěn)妥的?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_directory
choose_gc_options
JAVA_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 腳本的內容,內容比較多,這里拆分講解。

前置準備工作

首先是有關環(huán)境變量的配置獲取以及查找JAVA_HOME。

#!/bin/sh

#===========================================================================================
#?Java?Environment?Setting
#===========================================================================================
error_exit?()
{
????echo?"ERROR:?$1?!!"
????exit?1
}

find_java_home()
{
????#?uname?是獲取Linux內核參數的指令,不帶任何參數獲取當前操作系統(tǒng)的類型,比如Linux就是“Linux”的文本
????case?"`uname`"?in
????????Darwin)
????????????JAVA_HOME=$(/usr/libexec/java_home)
????????;;
????????*)
????????#?可以簡單認為獲取到javac命令的絕對路徑,然后執(zhí)行兩次cd..操作,以此作為JDK的路徑
????????#?比如?/opt/jdk/bin/javac?dirname?兩次之后就是?/opt/jdk
????????????JAVA_HOME=$(dirname?$(dirname?$(readlink?-f?$(which?javac))))
????????;;
????esac
}

#?調用函數
find_java_home

#?讀取JAVA命令的執(zhí)行地址
[?!?-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?導出的臨時環(huán)境變量,只適用當前SHELL連接
#?JAVA?命令的執(zhí)行地址,設置為環(huán)境變量
export?JAVA_HOME
export?JAVA="$JAVA_HOME/bin/java"
#?$0?代表當前的請求傳遞的第一個參數,根據上一個腳本可以知道是:${ROCKETMQ_HOME}/bin/runserver.sh
export?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內核參數的指令,不帶任何參數獲取當前操作系統(tǒng)的類型,比如Linux就是“Linux”的文本
????case?"`uname`"?in
????????Darwin)
????????????JAVA_HOME=$(/usr/libexec/java_home)
????????;;
????????*)
????????#?可以簡單認為獲取到javac命令的絕對路徑,然后執(zhí)行兩次?cd..?操作,以此作為JDK的路徑
????????#?比如?/opt/jdk/bin/javac?dirname?兩次之后就是?/opt/jdk
????????????JAVA_HOME=$(dirname?$(dirname?$(readlink?-f?$(which?javac))))
????????;;
????esac
}

uname 是獲取Linux內核參數的指令,不帶任何參數獲取當前操作系統(tǒng)的類型,比如Linux就是“Linux”的文本:

xander@xander:~$?uname?
Linux

這里通過uname 查找內核,如果是 darwin 的操作系統(tǒng)獲取路徑要特殊一些。而其他的方式則是$(dirname $(dirname $(readlink -f $(which javac))))層層查找:

[zxd@localhost?~]$?echo?$(dirname?$(dirname?$(readlink?-f?$(which?javac))));
/opt/jdk8

這些可以簡單認為獲取到javac命令的絕對路徑,然后執(zhí)行兩次cd..操作,以此作為JDK的路徑。最簡單的驗證方法是放到Linux上執(zhí)行一下:

[zxd@localhost?~]$?echo?$(readlink?-f?$(which?javac))
/opt/jdk8/bin/javac
[zxd@localhost?~]$?echo?$(dirname?$(dirname?$(readlink?-f?$(which?javac))));
/opt/jdk8

取JAVA命令的執(zhí)行地址

這里比較簡單,最后一句調用了error_exit函數,對應了第一個參數就是要打印的值。

#?讀取JAVA命令的執(zhí)行地址
[?!?-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?導出的臨時環(huán)境變量,只適用當前SHELL連接
#?取JAVA?命令的執(zhí)行地址,設置為環(huán)境變量
export?JAVA_HOME
export?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}

上面需要注意的點:

  1. export 導出的臨時環(huán)境變量,只適用當前SHELL連接。

  2. $0 代表當前j腳本名稱本身,../dirname 結合類似../../效果,$(dirname $0)/..為Rocketmq的安裝目錄地址。

  3. 需要把ROCKETMQ_HOMEconflib路徑告訴JDK找依賴包以及相關的配置文件。

JAVA="$JAVA_HOME/bin/java"
BASE_DIR=${ROCKETMQ_HOME}/bin
CLASSPATH=.:${ROCKETMQ_HOME}/bin/conf:${BASE_DIR}/lib/*:${CLASSPATH}

JVM 配置部分

之后往下的部分是JVM的配置部分,這部分我們拆分成兩個部分來講,最關鍵的是JVM參數配置部分,最開始是獲取JVM 的GC_LOG地址,這里用uname識別操作系統(tǒng),

#?在?Darwin?OS?上為?gc-log?初始化?RAMDisk?的大?。ㄒ?MB?為單位)
DIR_SIZE_IN_MB=600

choose_gc_log_directory()
{
????#?Darwin?操作系統(tǒng)需要特殊處理,忽略
????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?是否存在于其他系統(tǒng)上
????????????#?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系統(tǒng)中,會檢查 /dev/shm 是否存在系統(tǒng)上,如果是就把GC_LOG掛載到/dev/shm,否則就當前RocketMQ的安裝目錄GC_LOG_DIR=${BASE_DIR}。

這里補充一下/dev/shm 是什么?在Linux中,這塊空間起很大的作用,因為他不是硬盤而是一塊內存空間,默認為VM的一半大小,使用df -h命令可以看到:

xander@xander:~$?df?-h
Filesystem?????????????????????????Size??Used?Avail?Use%?Mounted?on
tmpfs??????????????????????????????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/shm
tmpfs??????????????????????????????5.0M?????0??5.0M???0%?/run/lock
/dev/sda2??????????????????????????2.0G??247M??1.6G??14%?/boot
tmpfs??????????????????????????????389M??4.0K??389M???1%?/run/user/1000

RocketMq為什么要使用這一塊空間?

tmpfs有以下優(yōu)勢:

  1. 動態(tài)文件系統(tǒng)的大小。

  2. tmpfs 文件系統(tǒng)會完全駐留在 RAM 中,擁有近似內存的讀寫速度。

而缺點僅僅是 tmpfs 數據在重新啟動之后不會保留,可以做一些腳本備份的操作。

tmpfs 和 /dev/shm 解釋

這塊空間的專業(yè)名詞叫做:tmpfs(虛擬內存系統(tǒng)),tmpfs最大的特點就是它的存儲空間在VM(virtual memory),VM是由linux內核里面的vm子系統(tǒng)管理的。

VM中又劃分為RM (real memory) 和 swap,RM 就是VM實際可用的內存空間,而swap是用于輔助VM在RM不夠的時候犧牲硬盤作為內存空間使用,同樣RM還會把不常用的數據放到Swap。

tmpfps = RM (real memory) + swap。

tmpfs默認的大小是RM的一半,假如你的物理內存是1024M,那么tmpfs默認的大小就是512M??梢酝ㄟ^mount命令擴大這塊空間大小。

tmpfps的存在意義是可以動態(tài)的擴容和縮小,并且只要不使用這塊空間它本身沒有任何的內存占用(0字節(jié))(零成本還好用),而一旦使用則可以把讀寫停留在內存保證數據瞬間完成,但是代價是這塊空間不具備記憶功能,重啟之后不會被保留。

參考資料:

  • 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版本處于實驗狀態(tài),這里選擇了比較穩(wěn)妥的?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版本處于實驗狀態(tài)。

個人理解是考慮到后續(xù)的版本垃圾收集器變動比較大,這里選擇了G1之前比較穩(wěn)妥的 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為響應時間優(yōu)先的垃圾回收器,適合于應用服務器,如網絡游戲,電商等和電信領域的應用。Rocketmq本身就是誕生于電商平臺使用CMS是比較穩(wěn)妥的。

CMS垃圾收集器特點

  • FUll GC大部分時間和應用程序并行,代價是增加CPU開銷。

  • 并發(fā)FULL GC短暫停頓。

  • 用戶線程和回收線程并行。

垃圾回收算法:標記清除算法

之后是對應的CMS常用優(yōu)化參數。

-XX:+UseCMSCompactAtFullCollection

設置此參數之后,CMS垃圾在進行了Full GC時,對老年代進行壓縮整理,處理掉內存碎片。RocketMq 的腳本也開啟了每次Full Gc之后進行碎片整理。

-XX:CMSFullGCsBeforeCompaction=1

FUll GC 之后對老年代進行壓縮整理,處理掉內存碎片。RocketMq 的默認應對策略是積極的進行內存碎片整理,縮小老年代的大小,因為RocketMq需要的是高響應時間。

CMSInitiatingOccupancyFraction=70

CMSInitiatingOccupancyFraction=70 表示當老年代達到70%時,觸發(fā)CMS垃圾回收。

  • 計算老年代最大使用率(_initiating_occupancy)

  • 大于等于0則直接取百分號

  • 小于0則根據公式來計算

如果使用默認值,則老年代觸發(fā)回收的比例是動態(tài)的,不同的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,就是軟飲用被頻繁回收導致觸發(fā)頻繁的GC,JVM發(fā)現每次反射分配的對象馬上就會被回收掉,然后接著又會通過代理生成代理對象,導致每次soft軟引用的對象一旦分配就會馬上被回收.

結論就是這個值為0,反射機制導致動態(tài)代理類不斷的被新增,但是這部分對象又被馬上回收掉,導致方法區(qū)的垃圾對象越來越多,會導致很多垃圾無法完全回收。

為什么RocketMq默認要把-XX:SoftRefLRUPolicyMSPerMB=0值設置為0?

我們接著分析,-XX:SoftRefLRUPolicyMSPerMB=0,但是使用的是服務器模式(-server),在server模式下,會用 -Xmx 參數獲取空閑空間大小。

空閑的空間越大,軟引用就會活的越久,如果設置的值過大,很可能因為框架反射類創(chuàng)建的軟引用過多但是因為存在空閑時間計算又沒法回收的情況。

把這個值設置為0,基本上就是說不讓反射產生的一些meta對象在GC之后回收不掉,直接通過1次GC就給他擺平了。但是個人角度來看未免過于極端,個人認為設置為0是不合適的。

參考案例: 這里在網上找到電子表格緩存業(yè)務因為設置 -XX:SoftRefLRUPolicyMSPerMB=0 導致的問題例子。 當JVM參數中配置了 -XX:SoftRefLRUPolicyMSPerMB=0 參數,這個參數是控制SoftReference緩存時間,而我們的電子表格的緩存都是存儲SoftReference里邊的,當設置了這個參數設置為0的時候,任意操作,只要是觸發(fā)了gc,這時候就會清空了電子表格緩存,導致即使在內存足夠的情況下,緩存也不生效了。

清除頻率可以用命令行參數 -XX:SoftRefLRUPolicyMSPerMB=<N>來控制,可以指定每兆堆空閑空間的 Soft reference 保持存活(一旦它飲用后不可達了)的毫秒數,這意味著每兆堆中的空閑空間中的 soft reference 會(在最后一個強引用被回收之后)存活1秒鐘。

當然并不是什么時候 -XX:SoftRefLRUPolicyMSPerMB=0都是錯的,因為 soft reference 只會在垃圾回收時才會被清除,而垃圾回收并不總在發(fā)生。系統(tǒng)默認為一秒,如果覺得客戶端不需要任何保留,改為 -XX:SoftRefLRUPolicyMSPerMB=0 可以及時清理干凈數據。

RocketMq的做法個人理解為想要讓垃圾回收盡可能的回收干凈對象,因為RocketMq并不是十分吃JVM堆內存,更多的是需要頁緩存,況且NameServ本身比較輕量級。

還有一個原因是軟引用這東西能不用就盡量不用,風險比較大。

-XX:+CMSClassUnloadingEnabled

老年代啟用CMS,但默認是不會回收永久代(Perm)的。此處啟用對Perm區(qū)啟用類回收,防止Perm區(qū)內存垃圾對象堆滿(需要與+CMSPermGenSweepingEnabled同時啟用)。

-XX:+CMSPermGenSweepingEnabled

同上,為了避免Perm區(qū)滿引起的Full GC,開啟并發(fā)收集器回收Perm區(qū)選項。

但是實際上這篇帖子上指出 https://stackoverflow.com/questions/3717937/cmspermgensweepingenabled-vs-cmsclassunloadingenabled[1]

-XX:+CMSClassUnloadEnabled -XX:+CMSPermGenSweepingEnabled 在 Java 1.7 中不可用,但是選項-XX:+CMSClassUnloadEnabled對于Java 1.7仍然有效。換句話說在JDK1.7之后被建議使用-XX:+CMSClassUnloadEnabled。

JVM1.7之前是什么情況?為什么會需要與+CMSPermGenSweepingEnabled同時啟用

下面這篇文章有評論進行了解釋:

用戶對問題“CMSPermGenSweepingEnabled vs CMSClassUnloadingEnabled”的回答 - 問答 - 騰訊云開發(fā)者社區(qū)-騰訊云 (tencent.com)[2]

1.6 JVM的CMSPermGenSweepingEnabled參數做的唯一事情就是打印消息,它的處理方式與1.5不同。要使CMSClassUnloadingEnabled生效,還必須設置UseConcMarkSweepGC

-XX:SurvivorRatio=8

CMS 用的是標記清除的算法,使用CMS還是傳統(tǒng)的新生代和老年代的分代概念,這里RocketMq用的是默認的分代分區(qū)策略,給了新生代更多的使用空間。它定義了新生代中Eden區(qū)域和Survivor區(qū)域(From幸存區(qū)或To幸存區(qū))的比例,默認為8,也就是說Eden占新生代的8/10,From幸存區(qū)和To幸存區(qū)各占新生代的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 是 穩(wěn)定版本,參見:http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html[3]

-XX:+PrintGC 是非穩(wěn)定版本。

-XX:+PrintGCDateStamps,日志中添加時間標志(日志每行開頭顯示自從JVM啟動以來的時間,單位為秒)

注意-XX:+PrintGCDateStamps ?打印GC發(fā)生時的時間戳[4],搭配 -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日志,后續(xù)再進行寫入就會從第一個文件開始替換。

150M日志足夠及時處理大部分問題,并且不會出現歷史日志長期駐留磁盤的問題。但是這個參數需要謹慎設置,如果設置過小容易導致關鍵GC 日志丟失。

等于JDK8的啟動參數

應該說是比較關鍵的版本,下面的參數對應JDK8的版本,很明顯都是圍繞G1垃圾收集器做的優(yōu)化:

????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"

第一行

對比發(fā)現JDK9版本沒啥區(qū)別:堆內存(初始堆內存)為 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%的空間來給新生代的對象晉升,如果經常發(fā)生新生代晉升失敗而導致 Full GC,那么可以適當調高此閾值。但是調高此值同時也意味著降低了老年代的實際可用空間

-XX:InitiatingHeapOccupancyPercent=30

觸發(fā)全局并發(fā)標記的老年代使用占比,默認值45%。

默認值45%的含義是也就是老年代占堆的比例超過45%。如果Mixed GC周期結束后老年代使用率還是超過45%,那么會再次觸發(fā)全局并發(fā)標記過程,這樣就會導致頻繁的老年代GC,影響應用吞吐量。

當然調大這個值的代價是可能導致年輕代謹升失敗而導致FULL GC。RocketMq使用30的設置是讓老年代提早的觸發(fā)GC并且回收垃圾。

-XX:SoftRefLRUPolicyMSPerMB=0

-XX:SoftRefLRUPolicyMSPerMB=N 這個參數在是JVM系統(tǒng)參數和垃圾收集器無關。

-XX:SoftRefLRUPolicyMSPerMB參數,可以指定每兆堆空閑空間的軟引用的存活時間,默認值是1000,也就是1秒??梢哉{低這個參數來觸發(fā)更早的回收軟引用。如果調高的話會有更多的存活數據,可能在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,選擇傳統(tǒng)的CMS+ParNew經典組合,不過NameServ 大部分時候干的不是什么重活,從保證高可用的角度是沒啥問題的。

  • JDK8使用的是G1垃圾收集器,并且參數都是盡可能的給新生代預留空間。

  • NameServ 新生代的壓力會比較大,整體思路是盡可能的減少垃圾,通過積極的GC保證垃圾盡可能被回收。

寫在最后

個人認為這種學習方式一舉多得,還可以看到不少Shell腳本的使用技巧,挺不錯的。

參考資料

[1]

https://stackoverflow.com/questions/3717937/cmspermgensweepingenabled-vs-cmsclassunloadingenabled: https://stackoverflow.com/questions/3717937/cmspermgensweepingenabled-vs-cmsclassunloadingenabled

[2]

用戶對問題“CMSPermGenSweepingEnabled vs CMSClassUnloadingEnabled”的回答 - 問答 - 騰訊云開發(fā)者社區(qū)-騰訊云 (tencent.com): https://cloud.tencent.com/developer/ask/sof/114185/answer/102415098

[3]

http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html: http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html

[4]

時間戳: https://so.csdn.net/so/search?q=時間戳&spm=1001.2101.3001.7020


【RocketMq】NameServ啟動腳本分析(Ver4.9.4)的評論 (共 條)

分享到微博請遵守國家法律
遂川县| 临西县| 泾阳县| 山阴县| 镇坪县| 两当县| 阿克苏市| 福安市| 松溪县| 河北区| 屏山县| 江山市| 宜章县| 澄城县| 仁化县| 冷水江市| 庆元县| 婺源县| 长阳| 临西县| 师宗县| 宣恩县| 陇南市| 乳山市| 阿克陶县| 樟树市| 抚宁县| 临江市| 托克托县| 新民市| 砀山县| 昌邑市| 阿瓦提县| 馆陶县| 措勤县| 宁强县| 望奎县| 新乡县| 张家口市| 拜城县| 土默特右旗|