天天看點

JVM性能調優詳解

性能調優

性能調優包含多個層次,比如:架構調優、代碼調優、JVM調優、資料庫調優、作業系統調優等。

架構調優和代碼調優是JVM調優的基礎,其中架構調優是對系統影響最大的。

性能調優基本上按照以下步驟進行:明确優化目标、發現性能瓶頸、性能調優、通過監控及資料統計工具獲得資料、确認是否達到目标。

何時進行JVM調優

遇到以下情況,就需要考慮進行JVM調優了:

Heap記憶體(老年代)持續上漲達到設定的最大記憶體值;

Full GC 次數頻繁;

GC 停頓時間過長(超過1秒);

應用出現OutOfMemory 等記憶體異常;

應用中有使用本地緩存且占用大量記憶體空間;

系統吞吐量與響應性能不高或下降。

JVM調優的基本原則

JVM調優是一個手段,但并不一定所有問題都可以通過JVM進行調優解決,是以,在進行JVM調優時,我們要遵循一些原則:

大多數的Java應用不需要進行JVM優化;

大多數導緻GC問題的原因是代碼層面的問題導緻的(代碼層面);

上線之前,應先考慮将機器的JVM參數設定到最優;

減少建立對象的數量(代碼層面);

減少使用全局變量和大對象(代碼層面);

優先架構調優和代碼調優,JVM優化是不得已的手段(代碼、架構層面);

分析GC情況優化代碼比優化JVM參數更好(代碼層面);

通過以上原則,我們發現,其實最有效的優化手段是架構和代碼層面的優化,而JVM優化則是最後不得已的手段,也可以說是對伺服器配置的最後一次“壓榨”。

JVM調優目标

調優的最終目的都是為了令應用程式使用最小的硬體消耗來承載更大的吞吐。jvm調優主要是針對垃圾收集器的收集性能優化,令運作在虛拟機上的應用能夠使用更少的記憶體以及延遲擷取更大的吞吐量。

延遲:GC低停頓和GC低頻率;

低記憶體占用;

高吞吐量;

其中,任何一個屬性性能的提高,幾乎都是以犧牲其他屬性性能的損為代價的,不可兼得。具體根據在業務中的重要性确定。

JVM調優量化目标

下面展示了一些JVM調優的量化目标參考執行個體:

Heap 記憶體使用率 <= 70%;

Old generation記憶體使用率<= 70%;

avgpause <= 1秒;

Full gc 次數0 或 avg pause interval >= 24小時 ;

注意:不同應用的JVM調優量化目标是不一樣的。

JVM調優的步驟

一般情況下,JVM調優可通過以下步驟進行:

分析GC日志及dump檔案,判斷是否需要優化,确定瓶頸問題點;

确定JVM調優量化目标;

确定JVM調優參數(根據曆史JVM參數來調整);

依次調優記憶體、延遲、吞吐量等名額;

對比觀察調優前後的差異;

不斷的分析和調整,直到找到合适的JVM參數配置;

找到最合适的參數,将這些參數應用到所有伺服器,并進行後續跟蹤。

以上操作步驟中,某些步驟是需要多次不斷疊代完成的。一般是從滿足程式的記憶體使用需求開始的,之後是時間延遲的要求,最後才是吞吐量的要求,要基于這個步驟來不斷優化,每一個步驟都是進行下一步的基礎,不可逆行之。

JVM參數

JVM調優最重要的工具就是JVM參數了。先來了解一下JVM參數相關内容。

-XX 參數被稱為不穩定參數,此類參數的設定很容易引起JVM 性能上的差異,使JVM存在極大的不穩定性。如果此類參數設定合理将大大提高JVM的性能及穩定性。

不穩定參數文法規則包含以下内容。

布爾類型參數值:

-XX: ’ '表示啟用該選項

-XX:- '-'表示關閉該選項

數字類型參數值:

-XX:=給選項設定一個數字類型值,可跟随機關,例如:'m’或’M’表示兆位元組;'k’或’K’千位元組;'g’或’G’千兆位元組。32K與32768是相同大小的。

字元串類型參數值:

-XX:=給選項設定一個字元串類型值,通常用于指定一個檔案、路徑或一系列指令清單。例如:-XX:HeapDumpPath=./dump.core

JVM參數解析及調優

比如以下參數示例:

-Xmx4g –Xms4g –Xmn1200m –Xss512k -XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:PermSize=100m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15

1

上面為Java7及以前版本的示例,在Java8中永久代的參數-XX:PermSize和-XX:MaxPermSize已經失效。這在前面章節中已經講到。

參數解析:

-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區進行多次複制,這樣可以增加對象再年輕代的存活時間,增加在年輕代即被回收的概論。

新生代、老生代、永久代的參數,如果不進行指定,虛拟機會自動選擇合适的值,同時也會基于系統的開銷自動調整。

可調優參數:

-Xms:初始化堆記憶體大小,預設為實體記憶體的1/64(小于1GB)。

-Xmx:堆記憶體最大值。預設(MaxHeapFreeRatio參數可以調整)空餘堆記憶體大于70%時,JVM會減少堆直到-Xms的最小限制。

-Xmn:新生代大小,包括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預清理階段執行達到這個時間時就會結束。

在設定的時候,如果關注性能開銷的話,應盡量把永久代的初始值與最大值設定為同一值,因為永久代的大小調整需要進行FullGC才能實作。

記憶體優化示例

當JVM運作穩定之後,觸發了FullGC我們一般會拿到如下資訊:

以上gc日志中,在發生fullGC之時,整個應用的堆占用以及GC時間。為了更加精确需多次收集,計算平均值。或者是采用耗時最長的一次FullGC來進行估算。上圖中,老年代空間占用在93168kb(約93MB),以此定為老年代空間的活躍資料。則其他堆空間的配置設定,基于以下規則來進行。

java heap:參數-Xms和-Xmx,建議擴大至3-4倍FullGC後的老年代空間占用。

永久代:-XX:PermSize和-XX:MaxPermSize,建議擴大至1.2-1.5倍FullGc後的永久帶空間占用。

新生代:-Xmn,建議擴大至1-1.5倍FullGC之後的老年代空間占用。

老年代:2-3倍FullGC後的老年代空間占用。

基于以上規則,則對參數定義如下:

java -Xms373m -Xmx373m -Xmn140m -XX:PermSize=5m -XX:MaxPermSize=5m

延遲優化示例

對延遲性優化,首先需要了解延遲性需求及可調優的名額有哪些。

應用程式可接受的平均停滞時間: 此時間與測量的Minor

GC持續時間進行比較。可接受的Minor GC頻率:Minor

GC的頻率與可容忍的值進行比較。

可接受的最大停頓時間:最大停頓時間與最差情況下FullGC的持續時間進行比較。

可接受的最大停頓發生的頻率:基本就是FullGC的頻率。

其中,平均停滞時間和最大停頓時間,對使用者體驗最為重要。對于上面的名額,相關資料采集包括:MinorGC的持續時間、統計MinorGC的次數、FullGC的最差持續時間、最差情況下,FullGC的頻率。

如上圖,Minor GC的平均持續時間0.069秒,MinorGC的頻率為0.389秒一次。

新生代空間越大,Minor GC的GC時間越長,頻率越低。如果想減少其持續時長,就需要減少其空間大小。如果想減小其頻率,就需要加大其空間大小。

這裡以減少了新生代空間10%的大小,來減小延遲時間。在此過程中,應該保持老年代和持代的大小不變化。調優後的參數如下變化:

java -Xms359m -Xmx359m -Xmn126m -XX:PermSize=5m -XX:MaxPermSize=5m

吞吐量調優

吞吐量調優主要是基于應用程式的吞吐量要求而來的,應用程式應該有一個綜合的吞吐名額,這個名額基于整個應用的需求和測試而衍生出來的。

評估目前吞吐量和目标差距是否巨大,如果在20%左右,可以修改參數,加大記憶體,再次從頭調試,如果巨大就需要從整個應用層面來考慮,設計以及目标是否一緻了,重新評估吞吐目标。

對于垃圾收集器來說,提升吞吐量的性能調優的目标就是盡可能避免或者很少發生FullGC或者Stop-The-World壓縮式垃圾收集(CMS),因為這兩種方式都會造成應用程式吞吐降低。盡量在MinorGC 階段回收更多的對象,避免對象提升過快到老年代。

調優工具

借助GCViewer日志分析工具,可以非常直覺地分析出待調優點。可從以下幾方面來分析:

Memory,分析Totalheap、Tenuredheap、Youngheap記憶體占用率及其他名額,理論上記憶體占用率越小越好;

Pause,分析Gc pause、Fullgc pause、Total pause三個大項中各名額,理論上GC次數越少越好,GC時長越小越好;

原文連結:《JVM性能調優詳解》

本文參考:

(1)

https://blog.csdn.net/jisuanjiguoba/article/details/80176223

(2)

https://juejin.im/post/59f02f406fb9a0451869f01c

《面試官》系列文章:

《JVM之記憶體結構詳解》

《面試官,不要再問我“Java GC垃圾回收機制”了》

《面試官,Java8 JVM記憶體結構變了,永久代到元空間》

《面試官,不要再問我“Java 垃圾收集器”了》

《Java虛拟機類加載器及雙親委派機制》

《Java記憶體模型(JMM)詳解》

《Java記憶體模型相關原則詳解》

《JVM性能調優詳解》

————————————————

版權聲明:本文為CSDN部落客「二師兄-公衆号-程式新視界」的原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結及本聲明。

原文連結:

https://blog.csdn.net/wo541075754/article/details/103007026