溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

虛擬機(jī)內(nèi)存問(wèn)題和Java虛擬機(jī)優(yōu)化案例分析

發(fā)布時(shí)間:2021-06-29 09:28:30 來(lái)源:億速云 閱讀:151 作者:chen 欄目:編程語(yǔ)言

這篇文章主要講解了“虛擬機(jī)內(nèi)存問(wèn)題和Java虛擬機(jī)優(yōu)化案例分析”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“虛擬機(jī)內(nèi)存問(wèn)題和Java虛擬機(jī)優(yōu)化案例分析”吧!

問(wèn)題總結(jié)

  1. 內(nèi)存多占1G左右,CPU利用率沒(méi)有明顯變化,但隨著CMS收集抖動(dòng),最高達(dá)40%,CPU load平均高出1.0左右

  2. 幾乎0停頓,相比于之前每隔5分鐘應(yīng)用停頓3-4s,調(diào)優(yōu)后的應(yīng)用幾乎沒(méi)有停頓時(shí)間,每次”stop the world” 由 youngGC 引起,最高也不過(guò)200+ms。

  3. GC總時(shí)間開銷顯著減小20%多,吞吐量顯著提升。

  4. 應(yīng)用超過(guò)500ms的請(qǐng)求響應(yīng)時(shí)間減少3%

參數(shù)對(duì)比

調(diào)優(yōu)前

-Dfile.encoding=UTF-8 -server -Xms8000M -Xmx8000M -Xmn5000M -Xss256K - 
XX:ThreadStackSize=256 -XX:StackShadowPages=8  -verbose:gc -XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseParallelGC

調(diào)優(yōu)后

-Dfile.encoding=UTF-8 -server -Xms10000M -Xmx10000M -Xmn5000M -
XX:MaxTenuringThreshold=1 -XX:SurvivorRatio=30 -XX:TargetSurvivorRatio=50 
-Xnoclassgc -Xss256K -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:PermSize=256m -
XX:MaxPermSize=256m  -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -
XX:CMSInitiatingOccupancyFraction=80  -XX:ParallelGCThreads=24  -XX:ConcGCThreads=24 
-XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark 
-XX:+ExplicitGCInvokesConcurrent -XX:+UseTLAB  -XX:TLABSize=64K

經(jīng)驗(yàn)分享

在開始前,我們需要一些數(shù)據(jù),因?yàn)閖vm調(diào)優(yōu)沒(méi)有一個(gè)標(biāo)準(zhǔn)的答案,根據(jù)實(shí)際應(yīng)用不同而不同,但也不是完全沒(méi)有章法可言,從一個(gè)實(shí)際的應(yīng)用,我們也可以找出一些規(guī)律來(lái),找出一些比較公用的,比如下面三條:

  1. 應(yīng)用平均和最大暫停時(shí)間(stop the world)。

  2. 吞吐量,真正運(yùn)行時(shí)間/(GC時(shí)間+真正運(yùn)行時(shí)間),而相對(duì)的GC開銷為:GC時(shí)間/(GC時(shí)間+真正運(yùn)行時(shí)間)

  3. URL的請(qǐng)求響應(yīng)時(shí)間

查看可以設(shè)置的所有參數(shù)

使用 -XX:+PrintFlagsFinal 參數(shù):可以查看當(dāng)前版本的虛擬機(jī)所能設(shè)置的所有參數(shù),還可以看到其默認(rèn)值。我使用6u26版本的java虛擬機(jī),一共有663個(gè)參數(shù),很多參數(shù)不必完全搞懂什么意思,而且很多優(yōu)化項(xiàng)在JDK6版本中已經(jīng)默認(rèn)開啟,所以我們只需要了解一些常用的即可。

最大堆的設(shè)置

  • 在單機(jī)web server的情況下,最大堆的設(shè)置建議在物理內(nèi)存的1/2到2/3之間,如果是16G的物理內(nèi)存的話,最大堆的設(shè)置應(yīng)該在8000M-10000M之間。

  • Java進(jìn)程消耗的總內(nèi)存肯定大于最大堆設(shè)置的內(nèi)存:堆內(nèi)存(Xmx)+ 方法區(qū)內(nèi)存(MaxPermSize)+ 棧內(nèi)存(Xss,包括虛擬機(jī)棧和本地方法棧)線程數(shù) + NIO direct memory + socket 緩存區(qū)(receive37KB,send25KB)+ JNI代碼 + 虛擬機(jī)和GC本身 = java的內(nèi)存。

  • 我們經(jīng)常碰到內(nèi)存巨高的線上問(wèn)題,留更多的內(nèi)存給“意外情況”是一件好事也是一件壞事,好事是更多的內(nèi)存可以給“錯(cuò)誤”提供擴(kuò)展空間,提升“容錯(cuò)性”,不至于馬上宕機(jī),但另一方面來(lái)說(shuō)技術(shù)人員不會(huì)第一時(shí)間收到“吃swap”這個(gè)告警信息。

GC策略的選擇

  • GC調(diào)優(yōu)是JVM調(diào)優(yōu)很重要的一步,當(dāng)前比較成熟的GC基本上有三種選擇,serial、Parallel和CMS,大型互聯(lián)網(wǎng)應(yīng)用基本上選擇后兩種,但Parallel的暫停時(shí)間實(shí)在太長(zhǎng),以 -Xmx 8000M -Xmn5000M 為例,平均一次youngGC需要100ms-200ms,而FullGC最長(zhǎng)需要6s,平均也要4s,雖然當(dāng)前沒(méi)有哪種GC策略能完全做到?jīng)]有暫停時(shí)間,但太長(zhǎng)的“stop the world”時(shí)間也讓人無(wú)法忍受

  • serial 和ParallelGC都是完全stop the world的GC,而CMS分為六步驟

虛擬機(jī)內(nèi)存問(wèn)題和Java虛擬機(jī)優(yōu)化案例分析

初始標(biāo)記(stop the world)

1093.220: [GC [1 CMS-initial-mark: 4113308K(5120000K)] 4180786K(10080000K), 0.0732930 
secs] [Times: user=0.07 sys=0.00, real=0.07 secs]

運(yùn)行時(shí)標(biāo)記(并發(fā))

1094.275: [CMS-concurrent-mark: 0.980/0.980 secs]
[Times: user=19.95 sys=0.51, real=0.98 secs]

運(yùn)行時(shí)清理(并發(fā))

1094.305: [CMS-concurrent-preclean: 0.028/0.029 secs] 
[Times: user=0.10 sys=0.02, real=0.03 secs]

CMS: abort preclean due to time 1099.643:
[CMS-concurrent-abortable-preclean: 5.288/5.337 secs] 
[Times: user=12.64 sys=1.19, real=5.34 secs]

重新標(biāo)記(stop the world,這個(gè)例子remark前執(zhí)行了一次youngGC)

1099.647: [GC [YG occupancy: 3308479 K (4960000 K)]
1099.648: [GC 1099.649: [ParNew: 3308479K->42384K(4960000K), 0.1420310 secs]
7421787K->4180693K(10080000K), 0.1447160 secs] [Times: user=2.69 sys=0.03, real=0.15 secs]

1099.793: [Rescan (parallel) , 0.0121000 secs]1099.805: [weak refs processing, 0.0664790 secs] 
[1 CMS-remark: 4138308K(5120000K)] 4180693K(10080000K), 0.2254870 secs] 
[Times: user=3.00 sys=0.05, real=0.23 secs]

運(yùn)行時(shí)清理(并發(fā))

1104.895: [CMS-concurrent-sweep: 4.970/5.020 secs] 
[Times: user=12.43 sys=1.05, real=5.02 secs]

復(fù)原(并發(fā))

1104.908: [CMS-concurrent-reset: 0.012/0.012 secs]
[Times: user=0.03 sys=0.01, real=0.01 secs]

要想知道應(yīng)用真正的停頓時(shí)間,可以使用PrintGCApplicationStoppedTime 參數(shù):

63043.344: [GC [PSYoungGen: 5009217K->34119K(5049600K)] 
5985479K->1034614K(8121600K), 0.1721890 secs] [Times: user=2.62 sys=0.01, real=0.18 secs]
  • Total time for which application threads were stopped: 0.1806210 seconds

  • Total time for which application threads were stopped: 0.0074870 seconds

這樣看來(lái),真正應(yīng)用暫停的時(shí)間要比stop the world時(shí)間還要稍長(zhǎng)一點(diǎn)點(diǎn)。

  • 本次調(diào)優(yōu)我基本上放棄了ParallelGC而選擇了CMS,CMS在old區(qū)很大的時(shí)候絕對(duì)是個(gè)利器,它不僅能大幅降低應(yīng)用“stop the world”時(shí)間,而且還能增加應(yīng)用的響應(yīng)時(shí)間和小部分吞吐量。

  • CMS還有一種增量模式:iCMS,適用于單CPU模式,會(huì)將回收動(dòng)作分作小塊進(jìn)行,但會(huì)增加回收時(shí)間,降低吞吐量,對(duì)于多CPU來(lái)說(shuō),可以不用考慮這種模式。


  • PrintFlagsFinal參數(shù)可以得知CMS的UseCMSCompactAtFullCollectionCMSParallelRemarkEnabled參數(shù),在JDK6里一直都是默認(rèn)為true的,所以我們不必顯示設(shè)置它。

  • 從維護(hù)角度來(lái)看,在設(shè)置參數(shù)之前,我們應(yīng)該首先看看這個(gè)參數(shù)是不是默認(rèn)已經(jīng)開啟了,如果默認(rèn)已經(jīng)開啟了我們就不必要再顯示設(shè)置它。

年輕代(eden和Survivor)、年老代的設(shè)置選擇了GC策略之后,年輕代和年老代的設(shè)置就很重要了,如果一味的追求響應(yīng)時(shí)間,可以盡量把年輕代調(diào)大一點(diǎn),youngGC的回收頻率減小了,但回收時(shí)間也增大了,5000M的年輕代,平均回收時(shí)間在150+ms,3000M的年輕代平均回收時(shí)間在90+ms。

如果一味的增大年輕代,CMS前提下的年老代的威力也發(fā)揮不出來(lái),更容易出現(xiàn)promotion failed導(dǎo)致一次FullGC。

但如果一味的調(diào)小年輕代,雖然單次回收時(shí)間減小,但回收頻率會(huì)陡增,應(yīng)用STW時(shí)間也會(huì)增加,總體年輕代回收的時(shí)間也可能會(huì)增大,所以調(diào)整年輕代和年老代的比例就是一個(gè)找平衡的過(guò)程

我的經(jīng)驗(yàn)是年輕代的比例在2/8到4/8之間,具體情況要看實(shí)際應(yīng)用情況而定。

我們都知道年輕代采用的是“copy”算法,有兩個(gè)survivor空間,每次回收總有一個(gè)是空的,另一個(gè)存放的是前幾次youngGC存留下來(lái)而且還不夠提升到old資格的對(duì)象,所以有三個(gè)參數(shù)很重要:

  • -XX:MaxTenuringThreshold=15:對(duì)象晉升到old的年齡,parallelGC和Serial默認(rèn)是15,CMS默認(rèn)是4,設(shè)置的越大,對(duì)象就越難進(jìn)入到old區(qū),youngGC反復(fù)copy的時(shí)間就會(huì)增大

  • -XX:SurvivorRatio=8,eden和survivor的比例,默認(rèn)是8,也就是說(shuō)如果eden為2400M,那么兩個(gè)survivor都為300M,如果MaxTenuringThreshold設(shè)置的很小,那么survivor區(qū)的使用率就會(huì)降低,反之,survivor的使用率就會(huì)增大。

  • -XX:TargetSurvivorRatio=80,survivor空間的利用率,默認(rèn)是50

如果設(shè)置SurvivorRatio為65536,MaxTenuringThreshold為0就表示禁止使用survivor空間,在這種模式下,對(duì)象直接進(jìn)入old區(qū),而且我發(fā)現(xiàn)在這種模式下,photo的resin啟動(dòng)時(shí)間大大減少,以前170s在這種模式下只需要90+s,足足降低了一半,因?yàn)檫@個(gè),我頓時(shí)對(duì)這種模式產(chǎn)生的興趣,但CMS的壓力就增大了,威力根本發(fā)揮不出來(lái)了,GC的時(shí)間沒(méi)有減少反而增加,remark的時(shí)間也增大到3s,最后不得不忍痛割愛放棄了這種模式。

-XX:+CMSScavengeBeforeRemark:這個(gè)參數(shù)還蠻重要的,它的意思是在執(zhí)行CMS remark之前進(jìn)行一次youngGC,這樣能有效降低remark的時(shí)間,之前我沒(méi)有加這個(gè)參數(shù),remark時(shí)間最大能達(dá)到3s,加上這個(gè)參數(shù)之后remark時(shí)間減少到1s之內(nèi)。

-XX:+UseCMSCompactAtFullCollection,用于指定在Full GC之后進(jìn)行內(nèi)存整理,內(nèi)存整理會(huì)使得垃圾收集停頓時(shí)間變長(zhǎng),CMS提供了另外一個(gè)參數(shù)。

-XX:CMSFullGCsBeforeCompaction,用于設(shè)置在執(zhí)行多少次不壓縮的Full GC之后,跟著再來(lái)一次內(nèi)存整理。

另外,我發(fā)現(xiàn)survivor空間并沒(méi)有像預(yù)期的那樣大(eden的1/8),通過(guò)跟蹤JVM的啟動(dòng)過(guò)程中發(fā)現(xiàn),JVM在一定的條件下(可能跟parallelGC和默認(rèn)SurvivorRatio有關(guān)會(huì)動(dòng)態(tài)調(diào)整survivor的大小,避免內(nèi)存浪費(fèi)。

感謝各位的閱讀,以上就是“虛擬機(jī)內(nèi)存問(wèn)題和Java虛擬機(jī)優(yōu)化案例分析”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)虛擬機(jī)內(nèi)存問(wèn)題和Java虛擬機(jī)優(yōu)化案例分析這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI