RocketMQ 4.2.0 broker JVM優化引數深入刨析
背景
線上使用RocketMQ有段時間了,可以說是相當穩定,除了程式碼和架構方面合理意外,其一系列的啟動優化引數也是非常值得研究,接下來以broker的啟動引數為例進行一次淺析。
啟動命令
/usr/lib/jvm/java-1.8.0-openjdk.x86_64/bin/java -server -Xms4g -Xmx4g -Xmn2g -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8 -verbose:gc -Xloggc:/dev/shm/mq_gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:MaxDirectMemorySize=15g -XX:-UseLargePages -XX:-UseBiasedLocking -Djava.ext.dirs=/usr/lib/jvm/java-1.8.0-openjdk.x86_64/jre/lib/ext:/home/user/rocketmq-all-4.2.0-master/bin/../lib -cp .:/home/user/rocketmq-all-4.2.0-master-b/bin/../conf:.:/usr/lib/jvm/java-1.8.0-openjdk.x86_64/lib:/usr/lib/jvm/java-1.8.0-openjdk.x86_64/jre/lib: org.apache.rocketmq.broker.BrokerStartup -c /home/user/rocketmq-all-4.2.0-master/conf/2m-noslave/broker-b.properties
可以看出,一條長長的命令,其中的JVM引數真實不少。
引數分析
-server
#指定以server方式啟動,這個是x64下面的必選項了,一般預設也是server模式,保險起見顯示加上,其效能要比client模式好太多了。
-Xms4g
-Xmx4g
#初始堆大小和最大對大小都是4g,以避免每次垃圾回收完成後JVM重新分配記憶體。
-Xmn2g
#設定年輕代大小為2G。整個JVM記憶體大小=年輕代大小 + 年老代大小 + 持久代大小(或MetaSpace空間大小)。持久代一般固定大小為64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8。
-XX:+UseG1GC
#開啟G1垃圾收集器
-XX:G1HeapRegionSize=16M
#指定堆中一個region分配的大小。每個Region被標記了E、S、O和H,說明每個Region在執行時都充當了一種角色,其中H是以往演算法中沒有的,它代表Humongous,這表示這些Region儲存的是巨型物件(humongous object,H-obj),當新建物件大小超過Region大小一半時,直接在新的一個或多個連續Region中分配,並標記為H。堆記憶體中一個Region的大小可以通過 -XX:G1HeapRegionSize引數指定,大小區間只能是1M、2M、4M、8M、16M和32M,總之是2的冪次方,如果G1HeapRegionSize為預設值,則在堆初始化時計算Region的實踐大小。G1 region 跨越1M-32M(2的指數),分配要求稍微超過4M。因此,8M的region的尺寸不足以避免humongous分配。需要16MB。
-XX:G1ReservePercent=25
#預留記憶體佔堆總大小的百分比,防止晉升失敗的情況,預設值是10%。一個JVM當在回收存活或者晉升物件的時候,棧區域溢位了就會發生失敗,因為堆的使用已經到達了最大值,不能再擴充套件。G1 GC設定上限預留記憶體,以防萬一"to-space"Survivor區域需要更多的空間。
-XX:InitiatingHeapOccupancyPercent=30
#整個堆使用達到百分之多少的時候,啟動GC週期. 基於整個堆,不僅僅是其中的某個代的佔用情況,G1根據這個值來判斷是否要觸發GC週期, 0表示一直都在GC,預設值是45(佔用了45%)。此處調節為30%,儘早觸發GC週期。0表示一直在GC。
-XX:SoftRefLRUPolicyMSPerMB=0
#這個引數比較有用的,官方解釋是:Soft reference在虛擬機器中比在客戶集中存活的更長一些。其清除頻率可以用命令列引數 -XX:SoftRefLRUPolicyMSPerMB=<N>來控制,這可以指定每兆堆空閒空間的 soft reference 保持存活(一旦它不強可達了)的毫秒數,這意味著每兆堆中的空閒空間中的 soft reference 會(在最後一個強引用被回收之後)存活1秒鐘。注意,這是一個近似值,因為 soft reference 只會在垃圾回收時才會被清除,而垃圾回收並不總在發生。系統預設為1一秒,沒必要等1秒,不用就立刻清除,改為 -XX:SoftRefLRUPolicyMSPerMB=0。
-XX:SurvivorRatio=8
#設定eden區域大小與其中一個survivor的比值是8。suvivor區有2個,即總的survivor和eden的比值是2:8。
-verbose:gc #或者 -XX:+PrintGC兩者作用相同。在虛擬機發生記憶體回收時在輸出裝置顯示資訊,格式如下:
[Full GC268K->168K(1984K), 0.0187390 secs]
-Xloggc:/dev/shm/mq_gc_%p.log #gc log的列印路徑和格式
-XX:+PrintGCDetails #相比 -XX:+PrintGC這個會列印更詳細的gc日誌資訊
-XX:+PrintGCDateStamps or XX:+PrintGCTimeStamps #GC發生時間戳,兩種不同格式
-XX:+PrintGCApplicationStoppedTime #列印gc一共停頓了多長時間
#通過上面5個引數的設定可以在指定路徑下獲得詳細的gc執行的日誌資訊
-XX:+PrintAdaptiveSizePolicy
#列印自適應收集的大小,預設關閉。配合-XX:-UseAdaptiveSizePolicy 來實現關閉自適應大小以及獲取在垃圾回收器日誌裡面額外的survivor空間統計資訊。
-XX:+UseGCLogFileRotation #預設關閉,即不進行切分。開啟日誌切分。
-XX:NumberOfGCLogFiles=5 #預設為0,即不做限制。開啟時控制log數量是5
-XX:GCLogFileSize=30m #預設為0,即不做任何限制。開啟時控制為30m
通過同時設定上面三項可以起到日誌迴圈滾動作用,可以防止耗盡伺服器的磁碟。
-XX:+AlwaysPreTouch
#啟用main之前JVM就會先訪問所有分配給它的記憶體,讓作業系統把記憶體真正的分配給JVM.後續JVM就可以順暢的訪問記憶體了。
JAVA程序啟動的時候,雖然我們可以為JVM指定合適的記憶體大小,但是這些記憶體作業系統並沒有真正的分配給JVM,而是等JVM訪問這些記憶體的時候,才真正分配,這樣會造成以下問題。
1、GC的時候,新生代的物件要晉升到老年代的時候,需要記憶體,這個時候作業系統才真正分配記憶體,這樣就會加大young gc的停頓時間; 2、可能存在記憶體碎片的問題。
-XX:MaxDirectMemorySize=15g
#當Direct ByteBuffer分配的堆外記憶體到達指定大小後,即觸發Full GC。該值是有上限的,預設是64M,最大為sun.misc.VM.maxDirectMemory(),在程式中中可以獲得-XX:MaxDirectMemorySize的設定的值。通過調大來減少直接堆觸發的Full GC。
direct ByteBuffer通過full gc來回收記憶體的,direct ByteBuffer會自己檢測情況而呼叫system.gc(),但是如果引數中使用了DisableExplicitGC那麼就無法回收該快記憶體了,-XX:+DisableExplicitGC標誌自動將System.gc()呼叫轉換成一個空操作,就是應用中呼叫System.gc()會變成一個空操作。那麼如果設定了就需要我們手動來回收記憶體了。
除了FULL GC還有別的能回收direct ByteBuffer嗎?CMS GC會回收Direct ByteBuffer的記憶體,CMS主要是針對old space空間的垃圾回收。但是是Oracle JDK 6u32以後的版本。
ByteBuffer有兩種:
heap ByteBuffer -> -XX:Xmx
1.一種是heap ByteBuffer,該類物件分配在JVM的堆記憶體裡面,直接由Java虛擬機器負責垃圾回收,
direct ByteBuffer -> -XX:MaxDirectMemorySize
2.一種是direct ByteBuffer是通過jni在虛擬機器外記憶體中分配的。通過jmap無法檢視該快記憶體的使用情況。只能通過top來看它的記憶體使用情況。
-XX:-OmitStackTraceInFastThrow
#禁用fast throw優化,加速線上問題排查。這是HotSpot VM專門針對異常做的一個優化,,稱為fast throw,當一些異常在程式碼裡某個特定位置被丟擲很多次的話,HotSpot Server Compiler(C2)會用fast throw來優化這個丟擲異常的地方,直接丟擲一個事先分配好的、型別匹配的物件,這個物件的message和stack trace都被清空。優點:丟擲這個異常非常快,不用額外分配記憶體,也不用爬棧。缺點:正好是需要知道哪裡出問題的時候看不到stack trace了,不利於排查問題
-XX:-UseLargePages
#禁用大頁(huge pages)優化。某些情況下會導致記憶體浪費或例項無法啟動。預設啟動。
-XX:-UseBiasedLocking
#JVM預設啟用偏向鎖。在競爭激烈的場合,偏向鎖會增加系統負擔(每次都要加一次是否偏向的判斷) 。
番外
在使用G1時,儘量不要配置 -XX:NewSize=4g -XX:MaxNewSize=5g展示使用者限制region的範圍在4-5G之間,因此限制了G1 GC的適應能力。如果G1需要去掉限制設定更小的值,它做不到;如果G1 GC需要增加空間範圍,超過了給它分配的空間,它做不到!
-Xss15120 #每增加一個執行緒(thread)就會立即消耗15M記憶體,而最佳值應該是128K,預設值好像是512k。
-XX:NewRatio=4 #表示新生代(eden + 2*survivor) 和老年代(不包括永久帶或metaspace)的比值
-XX:+PrintGCApplicationConcurrentTime #GC之間運行了多少時間