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來關閉該項優化。

原文鏈接來源:lousama


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM