1. 程式人生 > >JVM調優之經驗

JVM調優之經驗

在生產系統中,高吞吐和低延遲一直都是JVM調優的最終目標,但這兩者恰恰又是相悖的,魚和熊掌不可兼得,所以在調優之前要清楚舍誰而取誰。一般計算任務和元件服務會偏向高吞吐,而web展示則偏向低延遲才會帶來更好的使用者體驗。

本文從效能和經驗上來分享一下JVM引數的設定。

調優之前可以先用-XX:+PrintFlagsFinal來檢視虛擬機器是否預設開啟某引數,不同版本的JDK可能虛擬機器預設開啟的引數也略有不同,新學習一條神奇的引數的時候可以先去查詢一下引數是否預設開啟了。

$ java -server -XX:+PrintCommandLineFlags |grep XXXXXXX

也可以通過jinfo口令 jinfo -flags [pid]

來檢視

GC策略

目前來看還是CMS當道,吞吐率和響應時間闊以兼顧,G1嘛,雞丸雞丸,至今並沒有展現出one的實力,不過據貴裡某P8講G1在大堆(20G+)下表現更突出,停頓會顯著降低,可能之後隨著高記憶體越來越經濟和普及,G1才能名副其實的稱為雞one。

廢話少說,

-XX:+UseConcMarkSweepGC

設定CMS做為垃圾收集,CMS開啟後預設的新生代回收是ParNew,如果CMS出現“Concurrent Mode Failure”了還會啟用Serial Old做備胎。


-XX:CMSInitiatingOccupancyFraction=75

預設值是68,這個可以根據實際調優目標來調整,這個引數就比較應開始提到的,調優目標是降低延遲還是提高吞吐,如果是為了降低單次GC延遲,那麼這個值闊以再往低了調一些,不過調的太高可能導致老年代剩餘空間不夠招呼併發收集產生的浮動垃圾而頻繁的觸發Full GC。


-XX:+UseCMSInitiatingOccupancyOnly

使用CMS的話這個引數一定要加上,一定要加上,一定要加上,重要的事情說三遍,否則虛擬機器後面還是會自作聰明的自己計算上個引數的比值。


-XX:MaxTenuringThreshold=5

預設15,這個值是設定新生代物件存活了多少次young GC後可以進入老年代,值設的高的話可以使老年代增長緩慢,但YGC的次數會明顯增多,如果清楚YGC的執行頻率和大多數物件的最長生命週期,這個值可以設低些,讓那些物件早點進入老年代。 可以用-XX:+PrintTenuringDistribution來觀察一段時間,然後調整合適的值。

ps:有一種野路子是此值設為0,新生代GC次數少,速度快,就是老年代GC會更加頻繁一些,不過也最大利用了併發GC。不過我沒在生產這麼搞過,效果有待驗證。


-XX:+ExplicitGCInvokesConcurrent

這個引數是用來代替,-XX:DisableExplicitGC的,NIO許多地方會顯示的呼叫System.gc()來觸發一次Full GC。許多時候別的地方優化一萬點都賠不起這兒調上幾次的。ExplicitGCInvokesConcurrent這個引數是配合CMS使用的,開啟後System.gc()還是會觸發Full GC,不過並不是一個完全的stop-the-world的Full GC,而是併發的CMS GC。


記憶體設定

現在線上業務系統基本實體記憶體都是夠用的,不過物盡其用,我們調優就是爭取讓每M空間都發揮出最大的作用。記憶體的設定還是最直觀見效的。

-Xmx500m ,-Xms500m

最大堆記憶體和最小堆記憶體,這兩個值要設的一致,避免虛擬機器還要動態的計算分配記憶體空間。 PS:堆也不是越大越好,大堆帶來的後果就是單次GC會較長。


-Xmn250m

新生代大小,非G1收集器可以設定這個值,G1的官方建議是不要顯示分配新生代和老年代空間大小,因為G1會通過網格化記憶體來動態分配new/old區,官方認為不設定new size是最佳實踐。


-Xss2m

每個執行緒的棧空間大小,預設值是1m,一般不需要設定,除非有遞迴方法存在可能會爆棧。


-XX:PermSize=128m,-XX:MaxPermSize=256m

JDK8之前永久代的空間設定,Spring框架了大量依賴AOP的實現都用的動態代理生成位元組碼,所以設個最大值求保險。 不過JDK8之後取消了永久代,改為元空間(MetaSpace),這塊屬於本地記憶體,理論上可以利用系統剩餘的所有記憶體,不過跑了多個例項的話還是要設定一下為妙:

-XX:MetaspaceSize=128m,-XX:MaxMetaspaceSize=256m


-XX:MaxDirectMemorySize=128m

這個屬於對外記憶體,可以合理控制大小。Heap區總記憶體減去一個Survivor區的大小,不宜過大,否則可能heap size + Direct Memory Size把實體記憶體耗光。


-XX:SurvivorRatio=7

預設是8,新生代中Eden與Survivor的比值,過大的話可能Survivor存不下臨時物件而頻繁觸發分配擔保。可以根據GC日誌看實際情況。


PS: 關於記憶體大小的設定完全要根據各個機器和應用自身的情況來設定。 可以通過jstat -gc [pid] 2000 30,每2s輸出一次一共輸出30次記憶體情況,看看各個區域增長的速度,最大空間等資料來修改記憶體設定。


監控輸出

監控引數還是需要的,不然有時候線上偶爾OOM了真的不好重現。


-XX:+HeapDumpOnOutOfMemoryError,-XX:HeapDumpPath={path}

OOM的時候會輸出dump快照到{path}目錄,只需要指向目錄,檔名JVM會保持唯一性。


-XX:+PrintGCDetails,-Xloggc:logs/gc.log,-XX:+PrintGCTimeStamps,-XX:+PrintGCDateStamps

列印GC詳細記錄,-XX:+PrintGC 這個口令是簡單GC日誌,為了更容易定位問題,我們開啟Details模式,-Xloggc是把gc日誌輸出到指定檔案。 -XX:+PrintGCTimeStamps顯示的時間代表JVM啟動至記錄日誌的時間。 -XX:+PrintGCDateStamps則會新增上每行資訊的絕對日期。 其實開啟了-Xloggc的話會隱式的開啟-XX:+PrintGCTimeStamps,不過為了防止各版本JVM改動差異,還是顯示的設定出來保險。


-XX:-OmitStackTraceInFastThrow

這是個比較容易被忽略的引數,而沒有經驗的話又往往很難定位到原因。 JDK5之後JVM對異常做了一個優化,對於一些頻繁丟擲的異常,JIT重新編譯後會丟擲沒有堆疊資訊的異常,-server模式下是預設開啟的,因此在頻繁丟擲某個異常一段時間後,該優化開始起作用,即只丟擲沒有堆疊的異常資訊。 但由於該優化是JIT編譯後才啟用的,所以開始該異常的丟擲是有完整堆疊資訊的,但執行一段時間可能發現沒有任何堆疊資訊,很難定位,初次遇到很容易摸不到頭腦。 可以使用-XX:-OmitStackTraceInFastThrow來關閉該項優化。

原文