jvm調(diào)優(yōu)的過程(思路)

如果你對(duì)GC過程不熟悉,建議先看上一篇文章:GC-垃圾回收站 http://m.itdecent.cn/p/4ba980563ec0

什么情況下需要進(jìn)行jvm調(diào)優(yōu)?

1.Heap內(nèi)存(老年代)持續(xù)上漲達(dá)到設(shè)置的最大內(nèi)存值;
2.Full GC 次數(shù)頻繁;
3.GC 停頓時(shí)間過長(超過1秒);
4.應(yīng)用出現(xiàn)OutOfMemory 等內(nèi)存異常;
5.應(yīng)用中有使用本地緩存且占用大量內(nèi)存空間;
6.系統(tǒng)吞吐量與響應(yīng)性能不高或下降。
第3點(diǎn):使用Java的內(nèi)存分析工具,如JVisualVM、JMC或JProfiler。這些工具可以幫助您分析Java堆內(nèi)存中的對(duì)象,并找出哪些對(duì)象占用了大量的內(nèi)存。您可以使用這些工具來檢查應(yīng)用程序的本地緩存,并查看緩存中存儲(chǔ)的數(shù)據(jù)。

JVM調(diào)優(yōu)的基本原則

JVM調(diào)優(yōu)是一個(gè)手段,但并不一定所有問題都可以通過JVM進(jìn)行調(diào)優(yōu)解決,因此,在進(jìn)行JVM調(diào)優(yōu)時(shí),我們要遵循一些原則:

  • 大多數(shù)的Java應(yīng)用不需要進(jìn)行JVM優(yōu)化;
  • 大多數(shù)導(dǎo)致GC問題的原因是代碼層面的問題導(dǎo)致的(代碼層面);
  • 上線之前,應(yīng)先考慮將機(jī)器的JVM參數(shù)設(shè)置到最優(yōu);
  • 減少創(chuàng)建對(duì)象的數(shù)量(代碼層面);
  • 減少使用全局變量和大對(duì)象(代碼層面);
  • 優(yōu)先架構(gòu)調(diào)優(yōu)和代碼調(diào)優(yōu),JVM優(yōu)化是不得已的手段(代碼、架構(gòu)層面);
  • 分析GC情況優(yōu)化代碼比優(yōu)化JVM參數(shù)更好(代碼層面);
    通過以上原則,我們發(fā)現(xiàn),其實(shí)最有效的優(yōu)化手段是架構(gòu)和代碼層面的優(yōu)化,而JVM優(yōu)化則是最后不得已的手段,也可以說是對(duì)服務(wù)器配置的最后一次“壓榨”。
JVM調(diào)優(yōu)目標(biāo)

調(diào)優(yōu)的最終目的都是為了令應(yīng)用程序使用最小的硬件資源消耗來承載更大的吞吐。jvm調(diào)優(yōu)主要是針對(duì)垃圾收集器的收集性能優(yōu)化,令運(yùn)行在虛擬機(jī)上的應(yīng)用能夠使用更少的內(nèi)存以及延遲獲取更大的吞吐量。

延遲:GC低停頓和GC低頻率;
低內(nèi)存占用;
高吞吐量;
其中,任何一個(gè)屬性性能的提高,幾乎都是以犧牲其他屬性性能的損為代價(jià)的,不可兼得。具體根據(jù)在業(yè)務(wù)中的重要性確定。
注釋:GC停頓(GC pause)是指在垃圾回收過程中,暫停所有應(yīng)用程序線程的時(shí)間。這個(gè)停頓是為了讓垃圾回收器能夠有效地識(shí)別和回收不再使用的對(duì)象,從而釋放內(nèi)存空間。雖然GC停頓可能會(huì)影響應(yīng)用程序的性能,但一些垃圾收集器采用了降低停頓時(shí)間和提高吞吐量的優(yōu)化技術(shù),如并行垃圾回收和增量標(biāo)記等。這些技術(shù)可以減少GC停頓對(duì)應(yīng)用程序的影響,從而提高系統(tǒng)的性能和響應(yīng)速度。
GC低停頓和GC低頻率:即降低GC的停頓時(shí)間 或 降低GC的頻率

JVM調(diào)優(yōu)量化目標(biāo)

下面展示了一些JVM調(diào)優(yōu)的量化目標(biāo)參考實(shí)例:

  • Heap 內(nèi)存使用率 <= 70%;
    Old generation內(nèi)存使用率<= 70%;
    avgpause <= 1秒;
    Full gc 次數(shù)0 或 avg pause interval >= 24小時(shí) ;
    注意:不同應(yīng)用的JVM調(diào)優(yōu)量化目標(biāo)是不一樣的。
調(diào)優(yōu)工具 (參考GCViewer文章)

借助GCViewer日志分析工具,可以非常直觀地分析出待調(diào)優(yōu)點(diǎn)??蓮囊韵聨追矫鎭矸治觯?br> Memory,分析Totalheap、Tenuredheap、Youngheap內(nèi)存占用率及其他指標(biāo),理論上內(nèi)存占用率越小越好;
Pause,分析Gc pause、Fullgc pause、Total pause三個(gè)大項(xiàng)中各指標(biāo),理論上GC次數(shù)越少越好,GC時(shí)長越小越好;

JVM調(diào)優(yōu)的步驟

一般情況下,JVM調(diào)優(yōu)可通過以下步驟進(jìn)行:

  • 分析GC日志及dump文件,判斷是否需要優(yōu)化,確定瓶頸問題點(diǎn);
  • 確定JVM調(diào)優(yōu)量化目標(biāo);
  • 確定JVM調(diào)優(yōu)參數(shù)(根據(jù)歷史JVM參數(shù)來調(diào)整);
  • 依次調(diào)優(yōu)內(nèi)存、延遲、吞吐量等指標(biāo);
  • 對(duì)比觀察調(diào)優(yōu)前后的差異;
  • 不斷的分析和調(diào)整,直到找到合適的JVM參數(shù)配置;
  • 找到最合適的參數(shù),將這些參數(shù)應(yīng)用到所有服務(wù)器,并進(jìn)行后續(xù)跟蹤。
    以上操作步驟中,某些步驟是需要多次不斷迭代完成的。一般是從滿足程序的內(nèi)存使用需求開始的,之后是時(shí)間延遲的要求,最后才是吞吐量的要求,要基于這個(gè)步驟來不斷優(yōu)化,每一個(gè)步驟都是進(jìn)行下一步的基礎(chǔ),不可逆行之。
JVM參數(shù)

JVM調(diào)優(yōu)最重要的工具就是JVM參數(shù)了。先來了解一下JVM參數(shù)相關(guān)內(nèi)容。

-XX 參數(shù)被稱為不穩(wěn)定參數(shù),此類參數(shù)的設(shè)置很容易引起JVM 性能上的差異,使JVM存在極大的不穩(wěn)定性。如果此類參數(shù)設(shè)置合理將大大提高JVM的性能及穩(wěn)定性。

不穩(wěn)定參數(shù)語法規(guī)則包含以下內(nèi)容。
布爾類型參數(shù)值:
-XX:+ ‘+’表示啟用該選項(xiàng)
-XX:- ‘-‘表示關(guān)閉該選項(xiàng)

數(shù)字類型參數(shù)值:
-XX:=給選項(xiàng)設(shè)置一個(gè)數(shù)字類型值,可跟隨單位,例如:’m’或’M’表示兆字節(jié);’k’或’K’千字節(jié);’g’或’G’千兆字節(jié)。32K與32768是相同大小的。

字符串類型參數(shù)值:
-XX:=給選項(xiàng)設(shè)置一個(gè)字符串類型值,通常用于指定一個(gè)文件、路徑或一系列命令列表。例如:-XX:HeapDumpPath=./dump.core

JVM參數(shù)解析及調(diào)優(yōu)

比如以下參數(shù)示例:

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

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

參數(shù)解析:

-Xmx4g:堆內(nèi)存最大值為4GB。
-Xms4g:初始化堆內(nèi)存大小為4GB。
-Xmn1200m:設(shè)置年輕代大小為1200MB。增大年輕代后,將會(huì)減小年老代大小。此值對(duì)系統(tǒng)性能影響較大,Sun官方推薦配置為整個(gè)堆的3/8。
-Xss512k:設(shè)置每個(gè)線程的堆棧大小。JDK5.0以后每個(gè)線程堆棧大小為1MB,以前每個(gè)線程堆棧大小為256K。應(yīng)根據(jù)應(yīng)用線程所需內(nèi)存大小進(jìn)行調(diào)整。在相同物理內(nèi)存下,減小這個(gè)值能生成更多的線程。但是操作系統(tǒng)對(duì)一個(gè)進(jìn)程內(nèi)的線程數(shù)還是有限制的,不能無限生成,經(jīng)驗(yàn)值在3000~5000左右。
-XX:NewRatio=4:設(shè)置年輕代(包括Eden和兩個(gè)Survivor區(qū))與年老代的比值(除去持久代)。設(shè)置為4,則年輕代與年老代所占比值為1:4,年輕代占整個(gè)堆棧的1/5
-XX:SurvivorRatio=8:設(shè)置年輕代中Eden區(qū)與Survivor區(qū)的大小比值。設(shè)置為8,則兩個(gè)Survivor區(qū)與一個(gè)Eden區(qū)的比值為2:8,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/10
-XX:PermSize=100m:初始化永久代大小為100MB。
-XX:MaxPermSize=256m:設(shè)置持久代大小為256MB。
-XX:MaxTenuringThreshold=15:設(shè)置垃圾最大年齡。如果設(shè)置為0的話,則年輕代對(duì)象不經(jīng)過Survivor區(qū),直接進(jìn)入年老代。對(duì)于年老代比較多的應(yīng)用,可以提高效率。如果將此值設(shè)置為一個(gè)較大值,則年輕代對(duì)象會(huì)在Survivor區(qū)進(jìn)行多次復(fù)制,這樣可以增加對(duì)象再年輕代的存活時(shí)間,增加在年輕代即被回收的概論。
新生代、老生代、永久代的參數(shù),如果不進(jìn)行指定,虛擬機(jī)會(huì)自動(dòng)選擇合適的值,同時(shí)也會(huì)基于系統(tǒng)的開銷自動(dòng)調(diào)整。

可調(diào)優(yōu)參數(shù):

-Xms:初始化堆內(nèi)存大小,默認(rèn)為物理內(nèi)存的1/64(小于1GB)。

-Xmx:堆內(nèi)存最大值。默認(rèn)(MaxHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到-Xms的最小限制。

-Xmn:新生代大小,包括Eden區(qū)與2個(gè)Survivor區(qū)。

-XX:SurvivorRatio=1:Eden區(qū)與一個(gè)Survivor區(qū)比值為1:1。

-XX:MaxDirectMemorySize=1G:直接內(nèi)存。報(bào)java.lang.OutOfMemoryError: Direct buffer memory異??梢陨险{(diào)這個(gè)值。

-XX:+DisableExplicitGC:禁止運(yùn)行期顯式地調(diào)用System.gc()來觸發(fā)fulll GC。

注意: Java RMI的定時(shí)GC觸發(fā)機(jī)制可通過配置-Dsun.rmi.dgc.server.gcInterval=86400來控制觸發(fā)的時(shí)間。

-XX:CMSInitiatingOccupancyFraction=60:老年代內(nèi)存回收閾值,默認(rèn)值為68。

-XX:ConcGCThreads=4:CMS垃圾回收器并行線程線,推薦值為CPU核心數(shù)。

-XX:ParallelGCThreads=8:新生代并行收集器的線程數(shù)。

-XX:MaxTenuringThreshold=10:設(shè)置垃圾最大年齡。如果設(shè)置為0的話,則年輕代對(duì)象不經(jīng)過Survivor區(qū),直接進(jìn)入年老代。對(duì)于年老代比較多的應(yīng)用,可以提高效率。如果將此值設(shè)置為一個(gè)較大值,則年輕代對(duì)象會(huì)在Survivor區(qū)進(jìn)行多次復(fù)制,這樣可以增加對(duì)象再年輕代的存活時(shí)間,增加在年輕代即被回收的概論。

-XX:CMSFullGCsBeforeCompaction=4:指定進(jìn)行多少次fullGC之后,進(jìn)行tenured區(qū) 內(nèi)存空間壓縮。

-XX:CMSMaxAbortablePrecleanTime=500:當(dāng)abortable-preclean預(yù)清理階段執(zhí)行達(dá)到這個(gè)時(shí)間時(shí)就會(huì)結(jié)束。

在設(shè)置的時(shí)候,如果關(guān)注性能開銷的話,應(yīng)盡量把永久代的初始值與最大值設(shè)置為同一值,因?yàn)橛谰么拇笮≌{(diào)整需要進(jìn)行FullGC才能實(shí)現(xiàn)。

內(nèi)存優(yōu)化示例

當(dāng)JVM運(yùn)行穩(wěn)定之后,觸發(fā)了FullGC我們一般會(huì)拿到如下信息:
3.153:
[Full GC(Ergonomics)
[PSYoungGen:37887K->0k(359424K{新生代大小})]
[ParOldGen:84645K->93168K{FulIGC之后老年代空間占用}(184832K{老年代大小}]
122533K->93168K(544256K),
[Metaspace: 3135K->3135K{FulIGC后永久代空間占用}(1056768K{永久代大小})],
0.0773607 secs{FullGC時(shí)間}]
[Times: user=0.25 sys=0.02, real=0.07 secs]

以上gc日志中,在發(fā)生fullGC之時(shí),整個(gè)應(yīng)用的堆占用以及GC時(shí)間。為了更加精確需多次收集,計(jì)算平均值。或者是采用耗時(shí)最長的一次FullGC來進(jìn)行估算。上圖中,老年代空間占用在93168kb(約93MB),以此定為老年代空間的活躍數(shù)據(jù)。則其他堆空間的分配,基于以下規(guī)則來進(jìn)行。

java heap:參數(shù)-Xms和-Xmx,建議擴(kuò)大至3-4倍FullGC后的老年代空間占用。
永久代:-XX:PermSize和-XX:MaxPermSize,建議擴(kuò)大至1.2-1.5倍FullGc后的永久帶空間占用。
新生代:-Xmn,建議擴(kuò)大至1-1.5倍FullGC之后的老年代空間占用。
老年代:2-3倍FullGC后的老年代空間占用。

空間 倍數(shù)
總大小:3-4 倍活躍數(shù)據(jù)的大小
新生代:1-1.5 活躍數(shù)據(jù)的大小
老年代:2-3 倍活躍數(shù)據(jù)的大小
永久代 :1.2-1.5 倍Full GC后的永久代空間占用

基于以上規(guī)則,則對(duì)參數(shù)定義如下:
java -Xms373m -Xmx373m -Xmn140m -XX:PermSize=5m -XX:MaxPermSize=5m

延遲優(yōu)化示例

對(duì)延遲性優(yōu)化,首先需要了解延遲性需求及可調(diào)優(yōu)的指標(biāo)有哪些。

應(yīng)用程序可接受的平均停滯時(shí)間: 此時(shí)間與測量的Minor
GC持續(xù)時(shí)間進(jìn)行比較??山邮艿腗inor GC頻率:Minor
GC的頻率與可容忍的值進(jìn)行比較。
可接受的最大停頓時(shí)間:最大停頓時(shí)間與最差情況下FullGC的持續(xù)時(shí)間進(jìn)行比較。
可接受的最大停頓發(fā)生的頻率:基本就是FullGC的頻率。
其中,平均停滯時(shí)間和最大停頓時(shí)間,對(duì)用戶體驗(yàn)最為重要。對(duì)于上面的指標(biāo),相關(guān)數(shù)據(jù)采集包括:MinorGC的持續(xù)時(shí)間、統(tǒng)計(jì)MinorGC的次數(shù)、FullGC的最差持續(xù)時(shí)間、最差情況下,F(xiàn)ullGC的頻率。

吞吐量調(diào)優(yōu)

吞吐量調(diào)優(yōu)主要是基于應(yīng)用程序的吞吐量要求而來的,應(yīng)用程序應(yīng)該有一個(gè)綜合的吞吐指標(biāo),這個(gè)指標(biāo)基于整個(gè)應(yīng)用的需求和測試而衍生出來的。
評(píng)估當(dāng)前吞吐量和目標(biāo)差距是否巨大,如果在20%左右,可以修改參數(shù),加大內(nèi)存,再次從頭調(diào)試,如果巨大就需要從整個(gè)應(yīng)用層面來考慮,設(shè)計(jì)以及目標(biāo)是否一致了,重新評(píng)估吞吐目標(biāo)。
對(duì)于垃圾收集器來說,提升吞吐量的性能調(diào)優(yōu)的目標(biāo)就是盡可能避免或者很少發(fā)生FullGC或者Stop-The-World壓縮式垃圾收集(CMS),因?yàn)檫@兩種方式都會(huì)造成應(yīng)用程序吞吐降低。盡量在MinorGC 階段回收更多的對(duì)象,避免對(duì)象提升過快到老年代。

最后,回到文章的第一個(gè)問題,如果出現(xiàn)了以上問題,還可以通過以下的方法來分析:
1、Heap內(nèi)存(老年代)持續(xù)上漲達(dá)到設(shè)置的最大內(nèi)存值;
操作方法:jstat -gc <進(jìn)程ID>,其中<進(jìn)程ID>是Java進(jìn)程的ID(使用JVisualVM或JMC也可以查看)

分析:如果Java的堆內(nèi)存(老年代)持續(xù)上漲并達(dá)到設(shè)置的最大內(nèi)存值,這通常意味著垃圾回收器(GC)無法有效地回收無用的對(duì)象,或者應(yīng)用程序正在創(chuàng)建大量新的對(duì)象,超過了你設(shè)置的堆內(nèi)存大小。這可能會(huì)導(dǎo)致OutOfMemoryError錯(cuò)誤。

解決方案:
增加堆大?。喝绻愕膽?yīng)用程序確實(shí)需要更多的內(nèi)存來運(yùn)行,你可以嘗試增加堆的大小。但是,請(qǐng)注意,這可能會(huì)導(dǎo)致系統(tǒng)其他部分的內(nèi)存不足,從而影響性能。
優(yōu)化代碼:檢查你的代碼,看看是否有不必要的對(duì)象創(chuàng)建,或者是否有可以重用的對(duì)象。優(yōu)化你的代碼,以減少對(duì)象的創(chuàng)建和使用。
優(yōu)化垃圾收集器:根據(jù)你使用的Java版本,你可以嘗試調(diào)整垃圾收集器的參數(shù)。例如,你可以嘗試使用G1垃圾收集器或者ZGC,這些垃圾收集器在處理大量內(nèi)存時(shí)表現(xiàn)得更好。
使用Java的內(nèi)存分析工具:例如JVisualVM、JMC(Java Mission Control)或者JStack,這些工具可以幫助你找出內(nèi)存中的問題。
使用Java的Profiler:例如VisualVM、YourKit或者JProfiler,這些工具可以幫助你找出內(nèi)存中的問題,并給出優(yōu)化的建議。

2、Full GC 次數(shù)頻繁;
操作方法:jconsole <PID> ,在JConsole中,選擇“Java堆”選項(xiàng)卡,然后選擇“垃圾收集”部分。您將看到有關(guān)Full GC的詳細(xì)信息,包括次數(shù)和持續(xù)時(shí)間。如果使用的是云服務(wù),也可以在云服務(wù)監(jiān)控上查看,例如阿里云。

分析:Full GC(全局垃圾回收)可能會(huì)導(dǎo)致應(yīng)用程序性能下降,因?yàn)镕ull GC會(huì)暫停整個(gè)應(yīng)用程序,導(dǎo)致應(yīng)用程序停頓和響應(yīng)延遲。

解決方案:
系統(tǒng)承載高并發(fā)請(qǐng)求或處理大量數(shù)據(jù),導(dǎo)致Young GC無法處理,每次Young GC后存活的對(duì)象過多,內(nèi)存分配不合理,Survivor區(qū)過小,導(dǎo)致對(duì)象頻繁進(jìn)入老年代,頻繁觸發(fā)Full GC。解決方法是合理分配內(nèi)存,調(diào)大SurLo垃圾回收器使用率,調(diào)大Survivor區(qū)。
系統(tǒng)一次性加載過多數(shù)據(jù)進(jìn)內(nèi)存,導(dǎo)致頻繁有大對(duì)象進(jìn)入老年帶,觸發(fā)Full GC。解決方法是分批次加載數(shù)據(jù)。
內(nèi)存泄漏,創(chuàng)建大量對(duì)象,無法回收,一直占用老年代內(nèi)存,導(dǎo)致頻繁Full GC。解決方法是使用MAT工具分析內(nèi)存快照,找出內(nèi)存泄漏的原因并修復(fù)。
Metaspace(永久代)因加載類過多觸發(fā)Full GC。解決方法是合理設(shè)置類加載器的大小。
誤調(diào)用System.gc()觸發(fā)Full GC。解決方法是避免濫用System.gc(),因?yàn)镴VM并不一定會(huì)按照這個(gè)請(qǐng)求執(zhí)行。

3、GC 停頓時(shí)間過長(超過1秒);
操作方法:與查看Full GC次數(shù)相同,VisualVM是一個(gè)開源的Java虛擬機(jī)監(jiān)視、分析和調(diào)試工具,可以用于監(jiān)控JVM的性能和查看GC的詳細(xì)信息。您可以使用VisualVM的“Sampler”和“Profiler”功能來監(jiān)視應(yīng)用程序的性能,并查看GC的停頓時(shí)長。

分析:如果GC停頓時(shí)間過長(超過1秒),這可能會(huì)導(dǎo)致應(yīng)用程序性能下降,因?yàn)橥nD時(shí)間過長會(huì)導(dǎo)致應(yīng)用程序的響應(yīng)緩慢和停頓。以下是一些可能導(dǎo)致GC停頓時(shí)間過長的原因和解決方法:

解決方案
應(yīng)用程序的內(nèi)存分配不合理,導(dǎo)致GC需要花費(fèi)更多的時(shí)間來回收內(nèi)存。解決方法是合理分配內(nèi)存,避免過度使用內(nèi)存。
系統(tǒng)承載高并發(fā)請(qǐng)求或處理大量數(shù)據(jù),導(dǎo)致內(nèi)存中的對(duì)象過多,GC需要花費(fèi)更多的時(shí)間來處理這些對(duì)象。解決方法是分批次處理數(shù)據(jù),避免一次性加載過多數(shù)據(jù)進(jìn)內(nèi)存。
內(nèi)存泄漏,創(chuàng)建大量對(duì)象,無法回收,一直占用老年代內(nèi)存,導(dǎo)致GC需要花費(fèi)更多的時(shí)間來回收這些對(duì)象。解決方法是使用MAT工具分析內(nèi)存快照,找出內(nèi)存泄漏的原因并修復(fù)。
GC算法或垃圾收集器選擇不當(dāng),導(dǎo)致GC效率低下。解決方法是根據(jù)應(yīng)用程序的特點(diǎn)選擇適合的GC算法和垃圾收集器,并進(jìn)行調(diào)優(yōu)。
代碼中存在大量大對(duì)象的創(chuàng)建,導(dǎo)致GC需要花費(fèi)更多的時(shí)間來處理這些對(duì)象。解決方法是避免創(chuàng)建過多的大對(duì)象,或者將大對(duì)象分解成小對(duì)象進(jìn)行處理。

本文借鑒:
https://blog.csdn.net/agonie201218/article/details/123748148

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容