背景
隨著互聯(lián)網(wǎng)的發(fā)展以及java生態(tài)的不斷擴大,目前市面對java方面的人才擁有兩方面的要求,一個是擴大自己的知識面對常用的java架構(gòu)比較熟悉,擴張自己的知識廣度,往架構(gòu)方面發(fā)展,另一方面是深挖技術(shù)的深度,對java的底層的實現(xiàn)原理進行深挖,往技術(shù)專家方向發(fā)展。但是不論是往廣度還是往深度方向發(fā)展,對于java工程師來說,對java虛擬機(jvm)的了解是必不可少的,了解jvm參數(shù)會進行性能調(diào)優(yōu)對于我們來說越來越成為一種必備的技能了
jvm參數(shù)
堆大小設(shè)置
JVM 中最大堆大小有三方面限制:相關(guān)操作系統(tǒng)的數(shù)據(jù)模型(32-bt還是64-bit)限制;系統(tǒng)的可用虛擬內(nèi)存限制;系統(tǒng)的可用物理內(nèi)存限制。32位系統(tǒng)下,一般限制在1.5G~2G;64為操作系統(tǒng)對內(nèi)存無限制。我在Windows Server 2003 系統(tǒng),3.5G物理內(nèi)存,JDK5.0下測試,最大可設(shè)置為1478M,另外,整個堆大小=年輕代大小 + 年老代大小 + 持久代大?。↗DK8中已經(jīng)把持久代(PermGen Space) 干掉了,取而代之的元空間(Metaspace)。Metaspace占用的是本地內(nèi)存,不再占用虛擬機內(nèi)存)。持久代一般固定大小為64m,所以增大年輕代后,將會減小年老代大小。此值對系統(tǒng)性能影響較大,Sun官方推薦配置為整個堆的3/8。
-Xmx1800M? ## 設(shè)置JVM最大可用內(nèi)存為1800M?
-Xms1800M? ## 設(shè)置JVM初始內(nèi)存為1800M,建議與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存
-Xmn300M? ## 設(shè)置年輕代最大大小為300M(-Xmn(-XX:MaxNewSize))
-XX:NewSize=100M? ## JVM啟動時分配的新生代內(nèi)存為100M
-Xss512K? ## 設(shè)置每個線程的堆棧大小512K?。JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K。更具應(yīng)用的線程所需內(nèi)存大小進行調(diào)整。在相同物理內(nèi)存下,減小這個值能生成更多的線程。但是操作系統(tǒng)對一個進程內(nèi)的線程數(shù)還是有限制的,不能無限生成,經(jīng)驗值在3000~5000左右,所以根據(jù)這個規(guī)則可以合理設(shè)置自己本機的-Xss
-XX:NewRatio=4? ## 設(shè)置年輕代(包括Eden和兩個Survivor區(qū))與年老代的比值(除去持久代),設(shè)置為4,則年輕代與年老代所占比值為1:4,年輕代占整個堆棧(除去持久代)的1/5
-XX:SurvivorRatio=4? ## 設(shè)置年輕代中Eden區(qū)與Survivor區(qū)的大小比值。設(shè)置為4,則兩個Survivor區(qū)與一個Eden區(qū)的比值為2:4,一個Survivor區(qū)占整個年輕代的1/6
-XX:PermSize=300M? ## 設(shè)置持久代初始化大小為300M (JDK8中已經(jīng)把持久代(PermGen Space) 干掉了)
-XX:MaxPermSize=500M? ## 設(shè)置持久代最大大小為300M (JDK8中已經(jīng)把持久代(PermGen Space) 干掉了)
-XX:MaxTenuringThreshold=0? ## 設(shè)置垃圾最大年齡。如果設(shè)置為0的話,則年輕代對象不經(jīng)過Survivor區(qū),直接進入年老代。對于年老代比較多的應(yīng)用,可以提高效率。如果將此值設(shè)置為一個較大值,則年輕代對象會在Survivor區(qū)進行多次復制,這樣可以增加對象再年輕代的存活時間,增加在年輕代即被回收的概論。默認值為:15
回收器選擇
JVM給了三種選擇:串行收集器、并行收集器、并發(fā)收集器,但是串行收集器只適用于小數(shù)據(jù)量的情況,所以這里的選擇主要針對并行收集器和并發(fā)收集器。默認情況下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在啟動時加入相應(yīng)參數(shù)。JDK5.0以后,JVM會根據(jù)當前系統(tǒng)配置進行判斷。
-XX:+UseParallelGC? ## 選擇垃圾收集器為并行收集器。此配置僅對年輕代有效,即上述配置下,年輕代使用并發(fā)收集,而年老代仍舊使用串行收集。
XX:ParallelGCThreads=20? ## 配置并行收集器的線程數(shù),即:同時多少個線程一起進行垃圾回收。此值最好配置與處理器數(shù)目相等。
-XX:+UseParallelOldGC? ## 配置年老代垃圾收集方式為并行收集。JDK6.0支持對年老代并行收集。
XX:MaxGCPauseMillis=100? ## 設(shè)置每次年輕代垃圾回收的最長時間,如果無法滿足此時間,JVM會自動調(diào)整年輕代大小,以滿足此值。
-XX:+UseAdaptiveSizePolicy? ## 設(shè)置此選項后,并行收集器會自動選擇年輕代區(qū)大小和相應(yīng)的Survivor區(qū)比例,以達到目標系統(tǒng)規(guī)定的最低相應(yīng)時間或者收集頻率等,此值建議使用并行收集器時,一直打開
注:以上可以作為吞吐量優(yōu)先的并行收集器的典型配置
-XX:+UseConcMarkSweepGC? ## 設(shè)置年老代為并發(fā)收集。測試中配置這個以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此時年輕代大小最好用-Xmn設(shè)置。
-XX:+UseParNewGC? ## 設(shè)置年輕代為并行收集??膳cCMS收集同時使用。JDK5.0以上,JVM會根據(jù)系統(tǒng)配置自行設(shè)置,所以無需再設(shè)置此值。
-XX:CMSFullGCsBeforeCompaction=5? ## 由于并發(fā)收集器不對內(nèi)存空間進行壓縮、整理,所以運行一段時間以后會產(chǎn)生“碎片”,使得運行效率降低。此值設(shè)置運行多少次GC以后對內(nèi)存空間進行壓縮、整理。
-XX:+UseCMSCompactAtFullCollection? ## 打開對年老代的壓縮??赡軙绊懶阅?,但是可以消除碎片
注:以上可以作為響應(yīng)時間優(yōu)先的并發(fā)收集器的典型配置
另外:
-XX:+UseSerialGC? ## 設(shè)置串行收集器
-XX:GCTimeRatio=n? ## 設(shè)置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)
-XX:+CMSIncrementalMode? ## 設(shè)置為增量模式。適用于單CPU情況。
-XX:+CMSParallelRemarkEnabled? ## 降低標記停頓
-XX:+UseConcMarkSweepGC? ## 如果你啟用了CMSClassUnloadingEnabled ,垃圾回收會清理持久代,移除不再使用的classes。這個參數(shù)只有在?UseConcMarkSweepGC??也啟用的情況下才有用
輔助信息(輔助排查保存案發(fā)現(xiàn)場)
JVM提供了大量命令行參數(shù),打印信息,供調(diào)試使用。主要有以下一些:
-XX:+PrintGC
輸出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
??????????????? [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails
輸出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
??????????????? [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps??? -XX:+PrintGC:PrintGCTimeStamps 可與上面兩個混合使用
輸出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationConcurrentTime? ## 打印每次垃圾回收前,程序未中斷的執(zhí)行時間??膳c上面混合使用
輸出形式:Application time: 0.5291524 seconds
-XX:+PrintGCApplicationStoppedTime? ## 打印垃圾回收期間程序暫停的時間。可與上面混合使用
輸出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:PrintHeapAtGC? ## 打印GC前后的詳細堆棧信息
輸出形式:
34.702: [GC {Heap before gc invocations=7:
def new generation?? total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 99% used[0x1ebd0000, 0x21bce430, 0x21bd0000)
from space 6144K, 55% used[0x221d0000, 0x22527e10, 0x227d0000)
to?? space 6144K,?? 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
tenured generation?? total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K,?? 3% used[0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
def new generation?? total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K,?? 0% used[0x1ebd0000, 0x1ebd0000, 0x21bd0000)
from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
to?? space 6144K,?? 0% used [0x221d0000, 0x221d0000, 0x227d0000)
tenured generation?? total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K,?? 4% used?[0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
}
, 0.0757599 secs]
以上參考:https://blog.csdn.net/kidoo1012/article/details/54599046
jvm調(diào)優(yōu)步驟
目標:對JVM內(nèi)存的系統(tǒng)級的調(diào)優(yōu)主要的目的是減少GC的頻率和Full GC的次數(shù)。
注:如果滿足下面的指標,則一般不需要進行GC:
Minor GC執(zhí)行時間不到50ms;
Minor GC執(zhí)行不頻繁,約10秒一次;
Full GC執(zhí)行時間不到1s;
Full GC執(zhí)行頻率不算頻繁,不低于10分鐘1次;
原因分析
1.Full GC概念
會對整個堆進行整理,包括Young、Tenured和Perm。Full GC因為需要對整個堆進行回收,所以比較慢會導致應(yīng)用長時間的STW(Stop-The-World),因此應(yīng)該盡可能減少Full GC的次數(shù)。
2.導致Full GC的原因
1)年老代(Tenured)被寫滿
可能原因:參數(shù)-XX:MaxTenuringThreshold=0,不經(jīng)過Survivor區(qū)直接進入老年代,大對象直接進入老年代,一直存活的對象過多
分析:調(diào)優(yōu)時盡量讓對象在年輕代GC時被回收、讓對象在年輕代多存活一段時間和不要創(chuàng)建過大的對象及數(shù)組避免直接在舊生代創(chuàng)建對象 。
2)持久代空間不足
增大持久代空間,避免太多靜態(tài)對象 , 控制好年輕代和年老代的比例
3)System.gc()被顯示調(diào)用
垃圾回收不要手動觸發(fā),盡量依靠JVM自身的機制
4)上一次GC之后Heap的各域分配策略動態(tài)變化
盡量避免在運行時改變Heap的各域分配策略
調(diào)優(yōu)步驟
1.監(jiān)控GC的狀態(tài)
使用各種JVM工具,查看當前日志,分析當前JVM參數(shù)設(shè)置,并且分析當前堆內(nèi)存快照和gc日志,根據(jù)實際的各區(qū)域內(nèi)存劃分和GC執(zhí)行時間,覺得是否進行優(yōu)化。
舉一個例子: 系統(tǒng)崩潰前的一些現(xiàn)象:
每次垃圾回收的時間越來越長,由之前的10ms延長到50ms左右,F(xiàn)ullGC的時間也有之前的0.5s延長到4、5s
FullGC的次數(shù)越來越多,最頻繁時隔不到1分鐘就進行一次FullGC
年老代的內(nèi)存越來越大并且每次FullGC后年老代沒有內(nèi)存被釋放
之后系統(tǒng)會無法響應(yīng)新的請求,逐漸到達OutOfMemoryError的臨界值,這個時候就需要分析JVM內(nèi)存快照dump。
2.生成堆的dump文件
通過JMX的MBean生成當前的Heap信息,大小為一個3G(整個堆的大?。┑膆prof文件,如果沒有啟動JMX可以通過Java的jmap命令來生成該文件。
3.分析dump文件
打開這個3G的堆信息文件,顯然一般的Window系統(tǒng)沒有這么大的內(nèi)存,必須借助高配置的Linux,幾種工具打開該文件:
Visual VM
IBM HeapAnalyzer
JDK 自帶的Hprof工具
Mat(Eclipse專門的靜態(tài)內(nèi)存分析工具)推薦使用
備注:文件太大,建議使用Eclipse專門的靜態(tài)內(nèi)存分析工具Mat打開分析。
4.分析結(jié)果,判斷是否需要優(yōu)化
如果各項參數(shù)設(shè)置合理,系統(tǒng)沒有超時日志出現(xiàn),GC頻率不高,GC耗時不高,那么沒有必要進行GC優(yōu)化,如果GC時間超過1-3秒,或者頻繁GC,則必須優(yōu)化。
5.調(diào)整GC類型和內(nèi)存分配
如果內(nèi)存分配過大或過小,或者采用的GC收集器比較慢,則應(yīng)該優(yōu)先調(diào)整這些參數(shù),并且先找1臺或幾臺機器進行beta,然后比較優(yōu)化過的機器和沒有優(yōu)化的機器的性能對比,并有針對性的做出最后選擇。
6.不斷的分析和調(diào)整
通過不斷的試驗和試錯,分析并找到最合適的參數(shù),如果找到了最合適的參數(shù),則將這些參數(shù)應(yīng)用到所有服務(wù)器。
cms參數(shù)優(yōu)化步流程
1.針對JVM堆的設(shè)置,一般可以通過-Xms -Xmx限定其最小、最大值,為了防止垃圾收集器在最小、最大之間收縮堆而產(chǎn)生額外的時間,通常把最大、最小設(shè)置為相同的值;
2.年輕代和年老代將根據(jù)默認的比例(1:2)分配堆內(nèi)存, 可以通過調(diào)整二者之間的比率NewRadio來調(diào)整二者之間的大小,也可以針對回收代。
比如年輕代,通過 -XX:newSize -XX:MaxNewSize來設(shè)置其絕對大小。同樣,為了防止年輕代的堆收縮,我們通常會把-XX:newSize -XX:MaxNewSize設(shè)置為同樣大小。
3.年輕代和年老代設(shè)置多大才算合理
1)更大的年輕代必然導致更小的年老代,大的年輕代會延長普通GC的周期,但會增加每次GC的時間;小的年老代會導致更頻繁的Full GC
2)更小的年輕代必然導致更大年老代,小的年輕代會導致普通GC很頻繁,但每次的GC時間會更短;大的年老代會減少Full GC的頻率
如何選擇應(yīng)該依賴應(yīng)用程序?qū)ο笊芷诘姆植记闆r: 如果應(yīng)用存在大量的臨時對象,應(yīng)該選擇更大的年輕代;如果存在相對較多的持久對象,年老代應(yīng)該適當增大。但很多應(yīng)用都沒有這樣明顯的特性。
在抉擇時應(yīng)該根 據(jù)以下兩點:
(1)本著Full GC盡量少的原則,讓年老代盡量緩存常用對象,JVM的默認比例1:2也是這個道理 。
(2)通過觀察應(yīng)用一段時間,看其他在峰值時年老代會占多少內(nèi)存,在不影響Full GC的前提下,根據(jù)實際情況加大年輕代,比如可以把比例控制在1:1。但應(yīng)該給年老代至少預(yù)留1/3的增長空間。
4.在配置較好的機器上(比如多核、大內(nèi)存),可以為年老代選擇并行收集算法: -XX:+UseParallelOldGC 。
5.線程堆棧的設(shè)置:每個線程默認會開啟1M的堆棧,用于存放棧幀、調(diào)用參數(shù)、局部變量等,對大多數(shù)應(yīng)用而言這個默認值太了,一般256K就足用。
理論上,在內(nèi)存不變的情況下,減少每個線程的堆棧,可以產(chǎn)生更多的線程,但這實際上還受限于操作系統(tǒng)。