JVM調優經驗分享
前言
一、JVM調優知識背景簡介
二、JVM調優引數簡介
三、JVM調優目標
四、JVM調優經驗
結束語
前言
本次分享探討的JVM調優是指server端執行的JVM調優,適應版本為[1.6– 1.7], 不涉及最新的1.8版本。
假設執行緒池、連線池、程式程式碼等都已經做過優化,效果(系統吞吐量、響應效能)仍然不理想,我們就可以考慮JVM調優了。
一、 JVM調優知識背景簡介
1、堆與棧的概念
堆和棧是程式執行的關鍵:棧是執行時的單位,而堆是儲存的單位。
棧解決程式的執行問題,即程式如何執行,或者說如何處理資料;堆解決的是資料儲存的問題,即資料怎麼
放、放在哪兒。
在Java中一個執行緒就會相應有一個執行緒棧與之對應,而堆則是所有執行緒共享的。
棧儲存的資訊都是跟當前執行緒(或程式)相關資訊的,包括:區域性變數、程式執行狀態、方法返回值等等。
堆只負責儲存物件資訊。簡單來說:堆中存的是物件,棧中存的是基本資料型別和堆中物件的引用。
2、堆模型
虛擬機器中共劃分為三個代:
年輕代(Young Generation):用來存放JVM剛分配的Java物件。
年老代(Old Generation):年輕代中經過垃圾回收沒有回收掉的物件將被Copy到年老代。
持久代(PermGeneration):存放Class、Method元資訊,其大小跟專案的規模、類、方法的量有關。
3、堆記憶體分配策略
1.物件優先在Eden分配
如果Eden區不足分配物件,會做一個minorgc,回收記憶體,嘗試分配物件,如果依然不足分配,才分配到Old區。
2.大物件直接進入老年代
大物件是指需要大量連續記憶體空間的Java物件,最典型的大物件就是那種很長的字串及陣列,虛擬機器提供了一個-XX:PretenureSizeThreshold引數,令大於這個設定值的物件直接在老年代中分配。這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的記憶體拷貝(新生代採用複製演算法收集記憶體)。PretenureSizeThreshold引數只對Serial
3.長期存活的物件將進入老年代
在經歷了多次的Minor GC後仍然存活:在觸發了Minor GC後,存活物件被存入Survivor區在經歷了多次MinorGC之後,如果仍然存活的話,則該物件被晉升到Old區。虛擬機器給每個物件定義了一個物件年齡(Age)計數器。如果物件在Eden出生並經過第一次MinorGC後仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並將物件年齡設為1。物件在Survivor區中每熬過一次MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(預設為15歲)時,就會被晉升到老年代中。物件晉升老年代的年齡閾值,可以通過引數-XX:MaxTenuringThreshold來設定。
4.動態物件年齡判定
為了能更好地適應不同程式的記憶體狀況,虛擬機器並不總是要求物件的年齡必須達到MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。
5.MinorGC後Survivor空間不足就直接放入Old區6.空間分配擔保
在發生MinorGC時,虛擬機器會檢測之前每次晉升到老年代的平均大小是否大於老年代的剩餘空間大小,如果大於,則改為直接進行一次Full GC。如果小於,則檢視HandlePromotionFailure設定是否允許擔保失敗;如果允許,那隻會進行Minor GC;如果不允許,則也要改為進行一次Full GC。大部分情況下都還是會將HandlePromotionFailure開關開啟,避免FullGC過於頻繁。
4、JVM垃圾回收器簡介Java 語言的一大特點就是可以進行自動垃圾回收處理,而無需開發人員過於關注記憶體資源的釋放情況。自動垃圾收集雖然大大減輕了開發人員的工作量,但是也增加了軟體系統的負擔。
垃圾回收器常用的演算法:
1.引用計數法 (Reference Counting) 2.標記-清除演算法 (Mark-Sweep) 3.複製演算法 (Copying) 4.標記-壓縮演算法 (Mark-Compact) 5.增量演算法 (Incremental Collecting) 6.分代 (Generational Collecting)從不同角度分析垃圾收集器,可以將其分為不同的型別。
1.按執行緒數分,可以分為序列垃圾回收器和並行垃圾回收器; 2.按照工作模式分,可以分為併發式垃圾回收器和獨佔式垃圾回收器; 3.按碎片處理方式可分為壓縮式垃圾回收器和非壓縮式垃圾回收器; 4.按工作的記憶體區間,又可分為新生代垃圾回收器和老年代垃圾回收器。JVM預設的垃圾回收器是Parallel GC垃圾回收器,搭配為ParallelGC + ParallelOldGC ,滿足一般場景下JVM垃圾回收。
可以用以下指標評價一個垃圾處理器的好壞:
1.吞吐量; 2.垃圾回收器負載; 3.停頓時間; 4.垃圾回收頻率; 5.反應時間; 6.堆分配。5、 CMS(Concurrent Mark-Sweep)垃圾回收器簡介
CMS(Concurrent Mark-Sweep)是以犧牲吞吐量為代價來獲得最短回收停頓時間的垃圾回收器。對於要求伺服器響應速度的應用上,這種垃圾回收器非常適合。在啟動JVM引數加上-XX:+UseConcMarkSweepGC ,這個引數表示對於老年代的回收採用CMS。CMS採用的基礎演算法是:標記—清除。
CMS適應場景:
1. 相對較多存活時間較長的物件(老年代比較大);
2. 伺服器響應效能要求高;
6、 JVM GC組合方式
二、 JVM調優引數簡介
1、 JVM引數簡介
-XX 引數被稱為不穩定引數,之所以這麼叫是因為此類引數的設定很容易引起JVM 效能上的差異,使JVM 存在極大的不穩定性。如果此類引數設定合理將大大提高JVM 的效能及穩定性。
不穩定引數語法規則:
1.布林型別引數值
-XX:+<option> '+'表示啟用該選項
-XX:-<option> '-'表示關閉該選項
2.數字型別引數值:
-XX:<option>=<number> 給選項設定一個數字型別值,可跟隨單位,例如:'m'或'M'表示兆位元組;'k'或'K'千位元組;'g'或'G'千兆位元組。32K與32768是相同大小的。
3.字串型別引數值:
-XX:<option>=<string> 給選項設定一個字串型別值,通常用於指定一個檔案、路徑或一系列命令列表。
例如:-XX:HeapDumpPath=./dump.core
2、 JVM引數示例
配置: -Xmx4g –Xms4g –Xmn1200m –Xss512k -XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:PermSize=100m
-XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15
解析:
-Xmx4g:堆記憶體最大值為4GB。
-Xms4g:初始化堆記憶體大小為4GB 。
-Xmn1200m:設定年輕代大小為1200MB。增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8。
-Xss512k:設定每個執行緒的堆疊大小。JDK5.0以後每個執行緒堆疊大小為1MB,以前每個執行緒堆疊大小為256K。應根據應用執行緒所需記憶體大小進行調整。在相同實體記憶體下,減小這個值能生成更多的執行緒。但是作業系統對一個程序內的執行緒數還是有限制的,不能無限生成,經驗值在3000~5000左右。
-XX:NewRatio=4:設定年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設定為4,則年輕代與年老代所佔比值為1:4,年輕代佔整個堆疊的1/5
-XX:SurvivorRatio=8:設定年輕代中Eden區與Survivor區的大小比值。設定為8,則兩個Survivor區與一個Eden區的比值為2:8,一個Survivor區佔整個年輕代的1/10
-XX:PermSize=100m:初始化永久代大小為100MB。
-XX:MaxPermSize=256m:設定持久代大小為256MB。
-XX:MaxTenuringThreshold=15:設定垃圾最大年齡。如果設定為0的話,則年輕代物件不經過Survivor區,直接進入年老代。對於年老代比較多的應用,可以提高效率。如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件再年輕代的存活時間,增加在年輕代即被回收的概論。
三、 JVM調優目標
1. 何時需要做jvm調優?
什麼情況下需要對jvm做調優?
1. heap 記憶體(老年代)持續上漲達到設定的最大記憶體值;
2. Full GC 次數頻繁;
3. GC 停頓時間過長(超過1秒);
4. 應用出現OutOfMemory 等記憶體異常;
5. 應用中有使用本地快取且佔用大量記憶體空間;
6. 系統吞吐量與響應效能不高或下降。
2. JVM調優原則
JVM調優原則:
1、多數的Java應用不需要在伺服器上進行JVM優化;
2、多數導致GC問題的Java應用,都不是因為我們引數設定錯誤,而是程式碼問題;
3、在應用上線之前,先考慮將機器的JVM引數設定到最優(最適合);
4、減少建立物件的數量;
5、減少使用全域性變數和大物件;
6、JVM優化是到最後不得已才採用的手段;
7、在實際使用中,分析GC情況優化程式碼比優化JVM引數更好;
3. JVM調優目標
JVM調優目標 :
1. GC低停頓;
2. GC低頻率;
3. 低記憶體佔用;
4. 高吞吐量;
JVM調優量化目標(示例):
1. Heap 記憶體使用率 <= 70%;
2. Old generation記憶體使用率<= 70%;
3. avgpause <= 1秒;
4. Full gc 次數0 或 avg pause interval >= 24小時 ;
注意:不同應用,其JVM調優量化目標是不一樣的。四、 JVM調優經驗
1. JVM調優經驗總結
JVM調優的一般步驟為:第1步:分析GC日誌及dump檔案,判斷是否需要優化,確定瓶頸問題點;
第2步:確定JVM調優量化目標;
第3步:確定JVM調優引數(根據歷史JVM引數來調整);
第4步:調優一臺伺服器,對比觀察調優前後的差異;
第5步:不斷的分析和調整,直到找到合適的JVM引數配置;
第6步:找到最合適的引數,將這些引數應用到所有伺服器,並進行後續跟蹤。
2. JVM調優重要引數解析
注意:不同應用,其JVM最佳穩定引數配置是不一樣的。
配置: -server
-Xms12g -Xmx12g -XX:PermSize=500m -XX:MaxPermSize=1000m -Xmn2400m -XX:SurvivorRatio=1 -Xss512k -XX:MaxDirectMemorySize=1G
-XX:+DisableExplicitGC -XX:CompileThreshold=8000 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseCompressedOops -XX:CMSInitiatingOccupancyFraction=60 -XX:ConcGCThreads=4
-XX:MaxTenuringThreshold=10 -XX:ParallelGCThreads=8
-XX:+ParallelRefProcEnabled -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled
-XX:CMSMaxAbortablePrecleanTime=500 -XX:CMSFullGCsBeforeCompaction=4 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection
-XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/weblogic/gc/gc_$$.log
重要引數(可調優)解析:
-Xms12g:初始化堆記憶體大小為12GB。
-Xmx12g:堆記憶體最大值為12GB 。
-Xmn2400m:新生代大小為2400MB,包括 Eden區與2個Survivor區。
-XX:SurvivorRatio=1:Eden區與一個Survivor區比值為1:1。
-XX:MaxDirectMemorySize=1G:直接記憶體。報java.lang.OutOfMemoryError: Direct buffer memory 異常可以上調這個值。
-XX:+DisableExplicitGC:禁止執行期顯式地呼叫 System.gc() 來觸發fulll GC。
注意: Java RMI的定時GC觸發機制可通過配置-Dsun.rmi.dgc.server.gcInterval=86400來控制觸發的時間。
-XX:CMSInitiatingOccupancyFraction=60:老年代記憶體回收閾值,預設值為68。
-XX:ConcGCThreads=4:CMS垃圾回收器並行執行緒線,推薦值為CPU核心數。
-XX:ParallelGCThreads=8:新生代並行收集器的執行緒數。
-XX:MaxTenuringThreshold=10:設定垃圾最大年齡。如果設定為0的話,則年輕代物件不經過Survivor區,直接進入年老代。對於年老代比較多的應用,可以提高效率。如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件再年輕代的存活時間,增加在年輕代即被回收的概論。
-XX:CMSFullGCsBeforeCompaction=4:指定進行多少次fullGC之後,進行tenured區 記憶體空間壓縮。
-XX:CMSMaxAbortablePrecleanTime=500:當abortable-preclean預清理階段執行達到這個時間時就會結束。
3. 觸發Full GC的場景及應對策略
年輕代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC,對老年代GC稱為MajorGC,而Full GC是對整個堆來說的,在最近幾個版本的JDK裡預設包括了對永生帶即方法區的回收(JDK8中無永生帶了),出現Full GC的時候經常伴隨至少一次的Minor GC,但非絕對的。MajorGC的速度一般會比Minor GC慢10倍以上。
觸發Full GC的場景及應對策略:
1.System.gc()方法的呼叫,應對策略:通過-XX:+DisableExplicitGC來禁止呼叫System.gc ; 2.老年代代空間不足,應對策略:讓物件在Minor GC階段被回收,讓物件在新生代多存活一段時間,不要建立過大的物件及陣列; 3.永生區空間不足,應對策略:增大PermGen空間 4.GC時出現promotionfailed和concurrent mode failure,應對策略:增大survivor space 5.Minor GC後晉升到舊生代的物件大小大於老年代的剩餘空間,應對策略:增大Tenured space 或下調CMSInitiatingOccupancyFraction=606. 記憶體持續增漲達到上限導致Full GC ,應對策略:通過dumpheap 分析是否存在記憶體洩漏
4. Gc日誌分析工具
藉助GCViewer日誌分析工具,可以非常直觀地分析出待調優點。
可從以下幾方面來分析:
1.Memory,分析Totalheap、Tenuredheap、Youngheap記憶體佔用率及其他指標,理論上記憶體佔用率越小越好; 2.Pause ,分析Gc pause、Fullgc pause、Total pause三個大項中各指標,理論上GC次數越少越好,GC時長越小越好;5. MAT 堆記憶體分析工具
EclipseMemory Analysis Tools (MAT) 是一個分析Java堆資料的專業工具,用它可以定位記憶體洩漏的原因。
結束語
調優需謹慎,且調且珍惜。