5钞螟、JVM性能優(yōu)化
Java啟動參數(shù)共分為三類:
其一是標準參數(shù)(-),所有的JVM實現(xiàn)都必須實現(xiàn)這些參數(shù)的功能绸狐,而且向后兼容卤恳;
其二是非標準參數(shù)(-X),默認jvm實現(xiàn)這些參數(shù)的功能寒矿,但是并不保證所有jvm實現(xiàn)都滿足突琳,且不保證向后兼容;
其三是非Stable參數(shù)(-XX)符相,此類參數(shù)各個jvm實現(xiàn)會有所不同拆融,將來可能會隨時取消,需要慎重使用啊终。
JVM標準參數(shù)(-)
JVM的標準參數(shù)都是以”-“開頭镜豹,通過輸入”java -help”或者”java -?”,可以查看JVM標準參數(shù)列表蓝牲。
JVM非標準參數(shù)(-X)
通過”java -X”可以輸出非標準參數(shù)列表逛艰,如下所示:
JVM非Stable參數(shù)(-XX)
Java 6(update 21 oder 21之后)版本, HotSpot JVM 提供給了兩個新的參數(shù)搞旭,在JVM啟動后散怖,在命令行中可以輸出所有XX參數(shù)和值:
java -XX:+PrintFlagsInitial?-XX:+PrintFlagsInitial>>1.txt
這些參數(shù)可以被松散的聚合成三類:
行為參數(shù)(Behavioral Options):用于改變jvm的一些基礎行為菇绵;
性能調優(yōu)(Performance Tuning):用于jvm的性能調優(yōu);
調試參數(shù)(Debugging Options):一般用于打開跟蹤镇眷、打印咬最、輸出等jvm參數(shù),用于顯示jvm更加詳細的信息欠动;
行為參數(shù)(功能開關)
-XX:-DisableExplicitGC? 禁止調用System.gc()永乌;但jvm的gc仍然有效
-XX:+MaxFDLimit 最大化文件描述符的數(shù)量限制
-XX:+ScavengeBeforeFullGC?? 新生代GC優(yōu)先于Full GC執(zhí)行
-XX:+UseGCOverheadLimit 在拋出OOM之前限制jvm耗費在GC上的時間比例
-XX:-UseConcMarkSweepGC 對老生代采用并發(fā)標記交換算法進行GC
-XX:-UseParallelGC? 啟用并行GC
-XX:-UseParallelOldGC?? 對Full GC啟用并行,當-XX:-UseParallelGC啟用時該項自動啟用
-XX:-UseSerialGC??? 啟用串行GC
-XX:+UseThreadPriorities??? 啟用本地線程優(yōu)先級
性能調優(yōu)
-XX:LargePageSizeInBytes=4m 設置用于Java堆的大頁面尺寸
-XX:MaxHeapFreeRatio=70 GC后java堆中空閑量占的最大比例
-XX:MaxNewSize=size 新生成對象能占用內存的最大值
-XX:MaxPermSize=64m 老生代對象能占用內存的最大值
-XX:MinHeapFreeRatio=40 GC后java堆中空閑量占的最小比例
-XX:NewRatio=2? 新生代內存容量與老生代內存容量的比例
-XX:NewSize=125m? 新生代對象生成時占用內存的默認值
-XX:ReservedCodeCacheSize=32m??保留代碼占用的內存容量
-XX:ThreadStackSize=512 設置線程棧大小具伍,若為0則使用系統(tǒng)默認值
-XX:+UseLargePages? 使用大頁面內存
調試參數(shù)
-XX:-CITime 打印消耗在JIT編譯的時間
-XX:ErrorFile=./hs_err_pid<pid>.log 保存錯誤日志或者數(shù)據(jù)到文件中
-XX:-ExtendedDTraceProbes?? 開啟solaris特有的dtrace探針
-XX:HeapDumpPath=./java_pid.hprof? 指定導出堆信息時的路徑或文件名
-XX:-HeapDumpOnOutOfMemoryError 當首次遭遇OOM時導出此時堆中相關信息
-XX:OnError="<cmd args>;<cmd args>" 出現(xiàn)致命ERROR之后運行自定義命令
-XX:OnOutOfMemoryError=";"? 當首次遭遇OOM時執(zhí)行自定義命令
-XX:-PrintClassHistogram??? 遇到Ctrl-Break后打印類實例的柱狀信息翅雏,與jmap -histo功能相同
-XX:-PrintConcurrentLocks?? 遇到Ctrl-Break后打印并發(fā)鎖的相關信息,與jstack -l功能相同
-XX:-PrintCommandLineFlags? 打印在命令行中出現(xiàn)過的標記
-XX:-PrintCompilation?? 當一個方法被編譯時打印相關信息
-XX:-PrintGC??? 每次GC時打印相關信息
-XX:-PrintGC Details??? 每次GC時打印詳細信息
-XX:-PrintGCTimeStamps? 打印每次GC的時間戳
-XX:-TraceClassLoading? 跟蹤類的加載信息
-XX:-TraceClassLoadingPreorder?跟蹤被引用到的所有類的加載信息
-XX:-TraceClassResolution?? 跟蹤常量池
-XX:-TraceClassUnloading??? 跟蹤類的卸載信息
-XX:-TraceLoaderConstraints 跟蹤類加載器約束的相關信息
GC優(yōu)化的目的有兩個:
* 將轉移到老年代的對象數(shù)量降低到最腥搜俊望几;
* 減少full GC的執(zhí)行時間;
為了達到上面的目的萤厅,一般地橄抹,你需要做的事情有:
* 減少使用全局變量和大對象;
* 調整新生代的大小到最合適惕味;
* 設置老年代的大小為最合適楼誓;
* 選擇合適的GC收集器;
進行監(jiān)控和調優(yōu)的一般步驟為:
1名挥,監(jiān)控GC的狀態(tài)
使用各種JVM工具疟羹,查看當前日志,分析當前JVM參數(shù)設置禀倔,并且分析當前堆內存快照和gc日志榄融,根據(jù)實際的各區(qū)域內存劃分和GC執(zhí)行時間,覺得是否進行優(yōu)化蹋艺;
常用JVM工具:
(1)jstat
jstat可以實時顯示本地或遠程JVM進程中類裝載剃袍、內存黄刚、垃圾收集捎谨、JIT編譯等數(shù)據(jù)(如果要顯示遠程JVM信息,需要遠程主機開啟RMI支持)憔维。如果在服務啟動時沒有指定啟動參數(shù)-verbose:gc涛救,則可以用jstat實時查看gc情況。
jstat有如下選項:
-class:監(jiān)視類裝載业扒、卸載數(shù)量检吆、總空間及類裝載所耗費的時間
-gc:監(jiān)聽Java堆狀況,包括Eden區(qū)程储、兩個Survivor區(qū)蹭沛、老年代臂寝、永久代等的容量,以用空間摊灭、GC時間合計等信息
-gccapacity:監(jiān)視內容與-gc基本相同咆贬,但輸出主要關注java堆各個區(qū)域使用到的最大和最小空間
-gcutil:監(jiān)視內容與-gc基本相同,但輸出主要關注已使用空間占總空間的百分比
-gccause:與-gcutil功能一樣帚呼,但是會額外輸出導致上一次GC產(chǎn)生的原因
-gcnew:監(jiān)視新生代GC狀況
-gcnewcapacity:監(jiān)視內同與-gcnew基本相同掏缎,輸出主要關注使用到的最大和最小空間
-gcold:監(jiān)視老年代GC情況
-gcoldcapacity:監(jiān)視內同與-gcold基本相同,輸出主要關注使用到的最大和最小空間
-gcpermcapacity:輸出永久代使用到最大和最小空間
-compiler:輸出JIT編譯器編譯過的方法煤杀、耗時等信息
-printcompilation:輸出已經(jīng)被JIT編譯的方法
命令格式:jstat [option vmid [interval[s|ms] [count]]]
(2)jmap
用于顯示當前Java堆和永久代的詳細信息(如當前使用的收集器眷蜈,當前的空間使用率等)
?? -dump:生成java堆轉儲快照
?? -heap:顯示java堆詳細信息(只在Linux/Solaris下有效)
?? -F:當虛擬機進程對-dump選項沒有響應時,可使用這個選項強制生成dump快照(只在Linux/Solaris下有效)
(3)jstack
用于生成當前JVM的所有線程快照沈自,線程快照是虛擬機每一條線程正在執(zhí)行的方法,目的是定位線程出現(xiàn)長時間停頓的原因酌儒。
-F:當正常輸出的請求不被響應時,強制輸出線程堆棧
-l:除堆棧外酥泛,顯示關于鎖的附加信息
-m:如果調用到本地方法的話今豆,可以顯示C/C++的堆棧
命令格式:jstack [option] vmid
2,分析結果柔袁,判斷是否需要優(yōu)化
如果各項參數(shù)設置合理呆躲,系統(tǒng)沒有超時日志出現(xiàn),GC頻率不高捶索,GC耗時不高插掂,那么沒有必要進行GC優(yōu)化;如果GC時間超過1-3秒腥例,或者頻繁GC辅甥,則必須優(yōu)化;
注:如果滿足下面的指標燎竖,則一般不需要進行GC:
?? Minor GC執(zhí)行時間不到50ms璃弄;
?? Minor GC執(zhí)行不頻繁,約10秒一次构回;
?? Full GC執(zhí)行時間不到1s夏块;
?? Full GC執(zhí)行頻率不算頻繁,不低于10分鐘1次纤掸;
3脐供,調整GC類型和內存分配
如果內存分配過大或過小,或者采用的GC收集器比較慢借跪,則應該優(yōu)先調整這些參數(shù)政己,并且先找1臺或幾臺機器進行beta,然后比較優(yōu)化過的機器和沒有優(yōu)化的機器的性能對比掏愁,并有針對性的做出最后選擇歇由;
4卵牍,不斷的分析和調整
通過不斷的試驗和試錯,分析并找到最合適的參數(shù)
5沦泌,全面應用參數(shù)
如果找到了最合適的參數(shù)辽慕,則將這些參數(shù)應用到所有服務器,并進行后續(xù)跟蹤赦肃。
調優(yōu)實例:
實例1:
筆者昨日發(fā)現(xiàn)部分開發(fā)測試機器出現(xiàn)異常:java.lang.OutOfMemoryError: GC overhead limit exceeded溅蛉,這個異常代表:
GC為了釋放很小的空間卻耗費了太多的時間,其原因一般有兩個:1他宛,堆太小船侧,2,有死循環(huán)或大對象厅各;
筆者首先排除了第2個原因镜撩,因為這個應用同時是在線上運行的,如果有問題队塘,早就掛了袁梗。所以懷疑是這臺機器中堆設置太小憔古;
使用ps -ef |grep "java"查看遮怜,發(fā)現(xiàn):
該應用的堆區(qū)設置只有768m,而機器內存有2g鸿市,機器上只跑這一個java應用锯梁,沒有其他需要占用內存的地方。另外焰情,這個應用比較大陌凳,需要占用的內存也比較多;
筆者通過上面的情況判斷内舟,只需要改變堆中各區(qū)域的大小設置即可合敦,于是改成下面的情況:
跟蹤運行情況發(fā)現(xiàn),相關異常沒有再出現(xiàn)验游;
實例2:
一個服務系統(tǒng)充岛,經(jīng)常出現(xiàn)卡頓,分析原因批狱,發(fā)現(xiàn)Full GC時間太長:
jstat -gcutil:
S0???? ?S1???E???? O?????? ?P??????? YGC YGCT??????FGC??FGCT?GCT
12.16 0.00 5.18 63.78 20.32?54?? ???2.047???????5????????6.946? ?8.993
分析上面的數(shù)據(jù)裸准,發(fā)現(xiàn)Young GC執(zhí)行了54次展东,耗時2.047秒赔硫,每次Young GC耗時37ms,在正常范圍盐肃,而Full GC執(zhí)行了5次爪膊,耗時6.946秒权悟,每次平均1.389s,數(shù)據(jù)顯示出來的問題是:Full GC耗時較長推盛,分析該系統(tǒng)的是指發(fā)現(xiàn)峦阁,NewRatio=9,也就是說耘成,新生代和老生代大小之比為1:9榔昔,這就是問題的原因:
1,新生代太小瘪菌,導致對象提前進入老年代撒会,觸發(fā)老年代發(fā)生Full GC;
2师妙,老年代較大诵肛,進行Full GC時耗時較大;
優(yōu)化的方法是調整NewRatio的值默穴,調整到4怔檩,發(fā)現(xiàn)Full GC沒有再發(fā)生,只有Young GC在執(zhí)行蓄诽。這就是把對象控制在新生代就清理掉薛训,沒有進入老年代(這種做法對一些應用是很有用的,但并不是對所有應用都要這么做)
實例3:
一應用在性能測試過程中仑氛,發(fā)現(xiàn)內存占用率很高许蓖,F(xiàn)ull GC頻繁,使用sudo -u admin-H? jmap -dump:format=b,file=文件名.hprof pid 來dump內存调衰,生成dump文件膊爪,并使用Eclipse下的mat差距進行分析,發(fā)現(xiàn):
從圖中可以看出嚎莉,這個線程存在問題米酬,隊列LinkedBlockingQueue所引用的大量對象并未釋放,導致整個線程占用內存高達378m趋箩,此時通知開發(fā)人員進行代碼優(yōu)化赃额,將相關對象釋放掉即可。
JVM參數(shù)設置建議如下:
(1)-Xms和-Xmx的值設置成相等叫确,堆大小默認為-Xms指定的大小跳芳,默認空閑堆內存小于40%時,JVM會擴大堆到-Xmx指定的大兄衩恪飞盆;空閑堆內存大于70%時,JVM會減小堆到-Xms指定的大小。如果在Full GC后滿足不了內存需求會動態(tài)調整吓歇,這個階段比較耗費資源孽水。
(2)新生代盡量設置大一些,讓對象在新生代多存活一段時間城看,每次Minor GC 都要盡可能多的收集垃圾對象女气,防止或延遲對象進入老年代的機會,以減少應用程序發(fā)生Full GC的頻率测柠。
(3)老年代如果使用CMS收集器炼鞠,新生代可以不用太大,因為CMS的并行收集速度也很快轰胁,收集過程比較耗時的并發(fā)標記和并發(fā)清除階段都可以與用戶線程并發(fā)執(zhí)行簇搅。
(4)方法區(qū)大小的設置,1.6之前的需要考慮系統(tǒng)運行時動態(tài)增加的常量软吐、靜態(tài)變量等瘩将,1.7只要差不多能裝下啟動時和后期動態(tài)加載的類信息就行。
代碼實現(xiàn)方面凹耙,性能出現(xiàn)問題比如程序等待姿现、內存泄漏除了JVM配置可能存在問題,代碼實現(xiàn)上也有很大關系:
(1)避免創(chuàng)建過大的對象及數(shù)組:過大的對象或數(shù)組在新生代沒有足夠空間容納時會直接進入老年代肖抱,如果是短命的大對象备典,會提前出發(fā)Full GC。
(2)避免同時加載大量數(shù)據(jù)意述,如一次從數(shù)據(jù)庫中取出大量數(shù)據(jù)提佣,或者一次從Excel中讀取大量記錄,可以分批讀取荤崇,用完盡快清空引用拌屏。
(3)當集合中有對象的引用,這些對象使用完之后要盡快把集合中的引用清空术荤,這些無用對象盡快回收避免進入老年代倚喂。
(4)可以在合適的場景(如實現(xiàn)緩存)采用軟引用、弱引用瓣戚,比如用軟引用來為ObjectA分配實例:SoftReference objectA=new SoftReference(); 在發(fā)生內存溢出前端圈,會將objectA列入回收范圍進行二次回收,如果這次回收還沒有足夠內存子库,才會拋出內存溢出的異常舱权。
(5)避免產(chǎn)生死循環(huán),產(chǎn)生死循環(huán)后仑嗅,循環(huán)體內可能重復產(chǎn)生大量實例宴倍,導致內存空間被迅速占滿张症。
(6)盡量避免長時間等待外部資源(數(shù)據(jù)庫、網(wǎng)絡啊楚、設備資源等)的情況,縮小對象的生命周期浑彰,避免進入老年代恭理,如果不能及時返回結果可以適當采用異步處理的方式等。