關鍵業務系統的JVM參數推薦(2016熱冬版)

關鍵業務系統的JVM參數推薦(2016熱冬版)

10月 26, 2016 | Filed under 技術

在關鍵的業務系統裏,除了繼續追求技術人員最愛的高吞吐與低延時之外,系統的穩定性與出現問題時排查的便捷性也很重要。

這是本文的一個原則,後面也會一次又一次的強調,所以與網上其他的文章略有不同,請調優高手和運維老大們多指引。

 

前言1,資料

學習開源項目的啓動腳本是個不錯的主意,比如Cassandra家的, 附送一篇解釋它的文章

JVM調優的"標準參數"的各種陷阱 R大的文章,在JDK6時寫的,期待更新。

偶然翻到Linkedin工程師的一篇文章

更偶然翻到的一份不錯的參數列表

 

前言2, -XX:+PrintFlagsFinal打印參數值

當你在網上興沖沖找到一個可優化的參數時,先用-XX: +PrintFlagsFinal看看,它可能已經默認打開了,再找到一個,還是默認打開了...

JDK7與JDK8,甚至JDK7中的不同版本,有些參數值都不一樣,所以不要輕信網上任何文章,一切以生產環境同版本的JDK打出來的爲準。

經常以類似下面的語句去查看參數,偷懶不起應用,用-version代替。有些參數設置後會影響其他參數,所以查看時也把它帶上。

 

java -server -Xmx1024m -Xms1024m -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -version| grep ParallelGCThreads

 

前言3,關於默認值

JDK8會默認打開-XX:+TieredCompilation多層編譯,而JDK7則不會。JDK7u40以後的版本會默認打開-XX:+OptimizeStringConcat優化字符串拼接,而之前的則不打開。

對於這些參數,我的建議是順勢而爲,JDK在那個版本默認打開不打開總有它的理由。安全第一,沒有很好的因由,不要隨便因爲網上某篇文章的推薦(包括你現在在讀的這篇)就去設置。

 

1. 性能篇

先寫一些不那麼常見的,後面再來老生常談。

1.1 取消偏向鎖 -XX:-UseBiasedLocking

JDK1.6開始默認打開的偏向鎖,會嘗試把鎖賦給第一個訪問它的線程,取消同步塊上的synchronized原語。如果始終只有一條線程在訪問它,就成功略過同步操作以獲得性能提升。

但一旦有第二條線程訪問這把鎖,JVM就要撤銷偏向鎖恢復到未鎖定線程的狀態,詳見 JVM的Stop The World,安全點,黑暗的地底世界, 可以看到不少RevokeBiasd的紀錄,像GC一樣,會Stop The World的幹活,雖然只是很短很短的停頓,但對於多線程併發的應用,取消掉它反而有性能的提升和延時的極微的縮短,所以Cassandra就取消了它。
 

1.2 -XX:AutoBoxCacheMax=20000

Integer i = 3;這語句有着 int自動裝箱成Integer的過程,JDK默認只緩存 -128 ~ +127的int 和 long,超出範圍的數字就要即時構建新的Integer對象。設爲20000後,我們應用的QPS從48,000提升到50,000,足足4%的影響。詳見Java Integer(-128~127)值的==和equals比較產生的思考
 

1.3 啓動時訪問並置零內存頁面-XX:+AlwaysPreTouch

啓動時就把參數裏說好了的內存全部舔一遍,可能令得啓動時慢上一點,但後面訪問時會更流暢,比如頁面會連續分配,比如不會在晉升新生代到老生代時纔去訪問頁面使得GC停頓時間加長。不過這選項對大堆纔會更有感覺一點。
 

1.4 -XX:+PerfDisableSharedMem

Cassandra家的一個參數,一直沒留意,直到發生高IO時的JVM停頓。原來JVM經常會默默的在/tmp/hperf 目錄寫上一點statistics數據,如果剛好遇到PageCache刷盤,把文件阻塞了,就不能結束這個Stop the World的安全點了。用此參數可以禁止JVM寫statistics數據,代價是jps, jstat 用不了,只能用JMX取數據。有時用JMX取新生代老生代使用百分比還真沒jstat方便。詳見The Four Month Bug: JVM statistics cause garbage collection pauses
 

1.5 -Djava.security.egd=file:/dev/./urandom

此江湖偏方原用於Tomcat顯式使用SHA1PRNG算法時,初始因子從/dev/random讀取導致堵塞。而使用此設置後,額外效果是默認的SecureRandom算法也變成SHA1了。 SHA1PRNG 比 NativePRNG消耗小一半,synchronized的代碼少一半,所以沒特殊安全要求的話建議用SHA1。詳見 SecureRandom的江湖偏方與真實效果
 

1.6 不建議的參數

1. -XX:+AggressiveOpts是一些還沒默認打開的優化參數集合, -XX:AutoBoxCacheMax是其中的一項。但如前所述,關鍵系統裏不建議打開。雖然通過-XX:+AggressiveOpts 與 -XX:-AggressiveOpts 的對比,目前才改變了三個參數,但爲免以後某個版本的JDK裏默默改變更多激進的配置,還是不要了。

2. Linkined那種黑科技,先要解鎖VMOptions才能配置的就更不用說了,比如

 

-XX:+UnlockDiagnosticVMOptions -XX: ParGCCardsPerStrideChunk=32768

3. JIT Compile相關的參數,函數調用多少次之後開始編譯的閥值,內聯函數大小的閥值等等,不要亂改了。

4. -XX:+UseFastAccessorMethods,JDK6的優化,據說在多層編譯下還慢了,所以是默認關閉的。
 

5. -server,在64位linux中,你想設成-client都不行的,所以寫了也是白寫。
 

1.7 可選參數

1. -Djava.awt.headless=true,如果服務器上沒有屏幕,鍵盤,鼠標,又需要用到它們的時候,詳見在 Java SE 平臺上使用 Headless 模式

2. -XX:-UseCounterDecay,禁止JIT調用計數器衰減。默認情況下,每次GC時會對調用計數器進行砍半的操作,導致有些方法一直是個溫熱,可能永遠都達不到C2編譯的1萬次的閥值。

3. -XX:-TieredCompilation,禁止JDK8默認的多層編譯,在某些情況下因爲有些方法C1編譯後C2不再編譯,多層編譯反而比C2編譯慢,如果發現此情況可進行禁止。

 

2. GC篇

 

2.1 GC策略

爲了穩健,還是8G以下的堆還是CMS好了,G1的細節實現起來難度太大,從理論提出到現在都做了六七年了。

CMS真正可設的東西也不多,詳見JVM實用參數(七)CMS收集器

1.基本配置

 

-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly

因爲我們的監控系統會通過JMX監控內存達到90%的狀況(留點處理的時間),所以設置讓它75%就開始跑了,早點開始也能避免Full GC等意外情況(概念重申,這種主動的CMS GC,和JVM的老生代、永久代、堆外內存完全不能分配內存了而強制Full GC是不同的概念)。爲了讓這個設置生效,還要設置-XX:+UseCMSInitiatingOccupancyOnly,否則75只被用來做開始的參考值,後面還是JVM自己算。

2. -XX:MaxTenuringThreshold=2,這是GC裏改動效果最明顯的一個參數了。對象在Survivor區熬過多少次Young GC後晉升到年老代,JDK7裏看起來默認是6,跑起來好像變成了15。

Young GC是最大的應用停頓來源,而新生代裏GC後存活對象的多少又直接影響停頓的時間,所以如果清楚Young GC的執行頻率和應用裏大部分臨時對象的最長生命週期,可以把它設的更短一點,讓其實不是臨時對象的新生代長期對象趕緊晉升到年老代,別呆着。

用-XX:+PrintTenuringDistribution觀察下,如果後面幾代都差不多,就可以設小,比如JMeter裏是2。而我們的兩個系統裏一個設了2,一個設了6。

3. -XX:+ExplicitGCInvokesConcurrent, 但不要-XX:+DisableExplicitGC, 比如Netty之堆外內存掃盲篇,可見禁了system.gc() 未必是好事,只要自己的代碼裏沒有調它,也沒用什麼特別爛的類庫,真有人調了總有調的原因。-XX+ExplicitGCInvokesConcurrent 則在full gc時,並不全程停頓,依然只在ygc和兩個remark階段停頓,詳見JVM源碼分析之SystemGC完全解讀

4. -XX: ParallelRefProcEnabled , 默認爲false,並行的處理Reference對象,如WeakReference,除非在GC log裏出現Reference處理時間較長的日誌,否則效果不會很明顯,但我們總是要JVM儘量的並行,所以設了也就設了。
 

2.2 GC裏不建議設的參數

1. -XX:+CMSClassUnloadingEnabled,在CMS中清理永久代中的過期的Class而不等到Full GC,JDK7默認關閉而JDK8打開。看自己情況,比如有沒有運行動態語言腳本如Groovy產生大量的臨時類。它會增加CMS remark的暫停時間,所以如果新類加載並不頻繁,這個參數還是不開的好。

2. 用了CMS,新生代收集默認就是-XX:+UseParNewGC,不用自己設。

3. 併發收集線程數

 

ParallelGCThreads=8+( Processor - 8 ) ( 5/8 ),
ConcGCThreads = (ParallelGCThreads + 3)/4

比如雙CPU,六核,超線程就是24個處理器,小於8個處理器時ParallelGCThreads按處理器數量,大於時按上述公式ParallelGCThreads=18, ConcGCThreads=5。除了一些不在乎停頓時間的後臺輔助程序會特意把它減少,平時不建議動。

4. -XX:+CMSScavengeBeforeRemark,默認爲關閉,在CMS remark前,先執行一次minor GC將新生代清掉,這樣從老生代的對象引用到的新生代對象的個數就少了,停止全世界的CMS remark階段就短一些。如果看到GC日誌裏remark階段的時間超長,可以打開此項看看有沒有效果,否則還是不要打開了,白白多了次YGC。

5. -XX:CMSFullGCsBeforeCompaction,默認爲0,即每次full gc都對老生代進行碎片整理壓縮。Full GC 不同於 前面設置的75%老生代時觸發的CMS GC,只在System.gc(),老生代達到100%,老生代碎片過大無法分配空間給新晉升的大對象這些特殊情況裏發生,所以設爲每次都進行碎片整理是合適的,詳見此貼裏R大的解釋
 

2.3 內存大小的設置

這些關於大小的參數,給人感覺是最踏實可控的。

其實JVM除了顯式設置的-Xmx堆內存,還有一堆其他佔內存的地方(堆外內存,線程棧,永久代,二進制代碼cache),在容量規劃的時候要留意。

關鍵業務系統的服務器上內存一般都是夠的,所以儘管設得寬鬆點。

 
1. -Xmx, -Xms, 堆內存大小,2~4G均可,再大了注意GC時間。

2. -Xmn or -XX:NewSize and -XX:MaxNewSize or -XX:NewRatio, JDK默認新生代佔堆大小的1/3, 個人喜歡把對半分, 增大新生代的大小,能減少GC的頻率(但也會加大每次GC的停頓時間),主要是看老生代裏沒多少長期對象的話,佔2/3太多了。可以用-Xmn 直接賦值(等於-XX:NewSize and -XX:MaxNewSize同值的縮寫),或把NewRatio設爲1來對半分(但如果想設置新生代比老生代大就只能用-Xmn)。

3. -XX: PermSize=128m -XX:MaxPermSize=512m (JDK7)現在的應用有Hibernate/Spring這些鬧騰的傢伙AOP之後類都比較多,可以一開始就把初始值從64M設到128M,並設一個更大的Max值以求保險。

4. -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m(JDK8),JDK8的永生代幾乎可用完機器的所有內存,同樣設一個128M的初始值,512M的最大值保護一下。

 

2.4 其他內存大小等可選設置

1. -XX:SurvivorRatio 新生代中每個存活區的大小,默認爲8,即1/10的新生代 1/(SurvivorRatio+2),有人喜歡設小點省點給新生代,但要避免太小使得存活區放不下臨時對象而要晉升到老生代,還是從GC Log裏看實際情況了。

2. -Xss 在堆之外,線程佔用棧內存,默認每條線程爲1M(以前是256K)。存放方法調用出參入參的棧,局部變量,標量替換後掉局部變量等,有人喜歡設小點節約內存開更多線程。但反正內存夠也就不必要設小,有人喜歡再設大點,特別是有JSON解析之類的遞歸調用時不能設太小。

3. -XX:MaxDirectMemorySize,堆外內存/直接內存的大小,默認爲Heap區總內存減去一個Survivor區的大小,詳見Netty之堆外內存掃盲篇

4. -XX:ReservedCodeCacheSize, JIT編譯後二進制代碼的存放區,滿了之後就不再編譯。JDK7默認不開多層編譯48M,開了96M,而JDK8默認開多層編譯240M。可以在JMX裏看看CodeCache的大小,JDK7下的48M一般夠了,也可以把它設大點,反正內存多。

 

2.5 GC日誌

1.基本配置

 

-Xloggc:/dev/shm/gc-myapplication.log -XX:+PrintGCDateStamps -XX:+PrintGCDetails

詳見JVM實用參數(八)GC日誌,有人擔心寫GC日誌會影響性能,但測試下來實在沒什麼影響,還是留一份用來排查好。

到後來,又發現如果遇上高IO的情況,如果GC的時候,操作系統正在flush pageCache 到磁盤,也可能導致GC log文件被鎖住,從而讓GC結束不了。所以把它指向了/dev/shm 這種內存中文件系統,避免這種停頓,詳見Eliminating Large JVM GC Pauses Caused by Background IO Traffic

用+PrintGCDateStamps而不是PrintGCTimeStamps,打印可讀的日期而不是時間戳。

2. -XX:+PrintGCApplicationStoppedTime,它的名字沒起好,它除了打印清晰的GC停頓時間外,還可以打印其他的停頓時間,比如取消偏向鎖,class 被agent redefine,code deoptimization等等,有助於發現一些原來沒想到的問題,建議也加上。如果真的發現了一些不知什麼的停頓,再臨時加上"-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1" 找原因。

3. GC日誌默認會在重啓後清空,但有人擔心長期運行不重啓的應用會把文件弄得很大,有"-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=1M"的參數可以讓日誌滾動起來。但重啓後的文件名太混亂太讓人頭痛,所以還是不加。

 

3. 監控篇

JVM輸出的各種日誌,如果未指定路徑,通常會生成到運行應用的相同目錄,爲了避免有時候在不同的地方執行啓動腳本,一般將日誌路徑集中設到一個固定的地方。

 

3.1 -XX:+PrintCommandLineFlags

運維有時會對啓動參數做一些臨時的更改,將每次啓動的參數輸出到stdout,將來有據可查。
打印出來的是命令行裏設置了的參數以及因爲這些參數隱式影響的參數,比如開了CMS後,-XX:+UseParNewGC也被自動打開。
 

3.2 -XX:-OmitStackTraceInFastThrow

爲異常設置StackTrace是個昂貴的操作,所以當應用在相同地方拋出相同的異常N次(兩萬?)之後,JVM會對某些特定異常如NPE,數組越界等進行優化,不再帶上異常棧。此時,你可能會看到日誌裏一條條Nul Point Exception,而真正輸出完整棧的日誌早被滾動到不知哪裏去了,也就完全不知道這NPE發生在什麼地方,欲哭無淚。 所以,將它禁止吧。
 

3.3 coredump與 -XX:ErrorFile

JVM crash時,hotspot 會生成一個error文件,提供JVM狀態信息的細節。如前所述,將其輸出到固定目錄,避免到時會到處找這文件。文件名中的%p會被自動替換爲應用的PID

 

-XX:ErrorFile=${MYLOGDIR}/hs_err_%p.log

當然,更好的做法是生成coredump,從CoreDump能夠轉出Heap Dump 和 Thread Dump 還有crash的地方,非常實用。

在啓動腳本里加上 ulimit -c unlimited或其他的設置方式,如果有root權限,設一下輸出目錄更好

 

echo "/{MYLOGDIR}/coredump.%p" > /proc/sys/kernel/core_pattern

什麼?你不知道這coredump有什麼用?看來你是沒遇過JVM Segment Fault的幸福人。
 

3.4 -XX:+HeapDumpOnOutOfMemoryError

在Out Of Memory,JVM快死快死掉的時候,輸出Heap Dump到指定文件。不然開發很多時候還真不知道怎麼重現錯誤。

路徑只指向目錄,JVM會保持文件名的唯一性,叫java_pid${pid}.hprof。如果指向文件,而文件已存在,反而不能寫入。

 

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/

 

3.5 JMX

 

-Dcom.sun.management.jmxremote.port=${MY_JMX_PORT} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1

以上設置,只讓本地的Zabbix之類監控軟件通過JMX監控JVM,不允許遠程訪問。

4. 小結

4.1 性能相關

 

-XX:-UseBiasedLocking -XX:-UseCounterDecay -XX:AutoBoxCacheMax=20000 -XX:+PerfDisableSharedMem -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom

4.2 內存大小相關(JDK7)

 

-Xms4096m -Xmx4096m -Xmn2048m -XX:MaxDirectMemorySize=4096m-XX: PermSize=256m -XX:MaxPermSize=512m -XX:ReservedCodeCacheSize=240M

4.3 CMS GC 相關

 

-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=6 -XX:+ExplicitGCInvokesConcurrent -XX:+ParallelRefProcEnabled

4.4 GC 日誌 相關

 

-Xloggc:/dev/shm/app-gc.log -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails

4.5 異常 日誌 相關

 

-XX:-OmitStackTraceInFastThrow -XX:ErrorFile=${LOGDIR}/hs_err_%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/

4.6 JMX相關

見3.5

歡迎訂閱我新開的公衆號,請搜索 “春天的旁邊” 訂閱。

《關鍵業務系統的JVM啓動參數推薦》,轉載請保留鏈接。

發佈了13 篇原創文章 · 獲贊 12 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章