摘要:原文鏈接本篇是專家系列的第三篇。但是,請(qǐng)記住調(diào)優(yōu)是不得已時(shí)的選擇??s短耗時(shí)的單次執(zhí)行與相比,耗時(shí)有較明顯的增加。創(chuàng)建文件過程中,進(jìn)程會(huì)中斷,因此不要在正常運(yùn)行時(shí)系統(tǒng)上做此操作。因此校驗(yàn)結(jié)果并根據(jù)具體的服務(wù)需要,決定是否要進(jìn)行調(diào)優(yōu)。
原文鏈接:http://www.cubrid.org/blog/dev-platform/how-to-tune-java-garbage-collection/
本篇是GC專家系列的第三篇。在第一篇理解Java垃圾回收中我們學(xué)習(xí)了幾種不同的GC算法的處理過程,GC的工作方式,新生代與老年代的區(qū)別。所以,你應(yīng)該已經(jīng)了解了JDK 7中的5種GC類型,以及每種GC對(duì)性能的影響。
在第二篇Java垃圾回收的監(jiān)控中介紹了在真實(shí)場(chǎng)景中JVM是如何運(yùn)行GC,如何監(jiān)控GC數(shù)據(jù)以及有哪些工具可用來方便進(jìn)行GC監(jiān)控。
在本篇中,我將基于真實(shí)的案例來介紹一些GC調(diào)優(yōu)的最佳選項(xiàng)。寫本篇文章時(shí),我假設(shè)你已經(jīng)理解了前兩篇的內(nèi)容。為了深入理解本部分內(nèi)容,你最好先瀏覽一下前兩篇的內(nèi)容——如果你尚未了解的話。
GC調(diào)優(yōu)是必須的嗎更精確的說,基于Java的服務(wù)是否一定需要GC調(diào)優(yōu)?應(yīng)該說,GC調(diào)優(yōu)并非所有Java服務(wù)都必須做的事情。當(dāng)然這是基于你已經(jīng)使用了下面的選項(xiàng)或事實(shí):
通過-Xms和-Xmx選項(xiàng)指定了內(nèi)存大小
使用了-server選項(xiàng)
系統(tǒng)未產(chǎn)生太多超時(shí)日志
也就是說,如果你未設(shè)置內(nèi)存大小并且你的系統(tǒng)產(chǎn)生了過多的超時(shí)日志,恭喜你需要為你的系統(tǒng)執(zhí)行GC調(diào)優(yōu)。
但是,請(qǐng)記?。?strong>GC調(diào)優(yōu)是不得已時(shí)的選擇。
思考一下GC調(diào)優(yōu)的深層原因。垃圾回收器會(huì)去清理Java中創(chuàng)建的對(duì)象。GC需要清理的對(duì)象數(shù)據(jù)以及GC執(zhí)行的次數(shù)取決于應(yīng)用創(chuàng)建對(duì)象的多少。因此,為了控制GC的執(zhí)行,首先你需要減少對(duì)象的創(chuàng)建。
俗話說“積重難返”。所以我們需要從小處著手,否則它們將不斷壯大直到難以管理。
應(yīng)該多使用StringBuilder和StringBuffer對(duì)象替代String。
減少不必要的日志輸出。
即便如此,面對(duì)有些場(chǎng)景我們依然無能為力。我們知道解析XML和JSON會(huì)占用大量的內(nèi)存空間。即便我們盡可能少的使用String,盡可能好的優(yōu)化日志輸出,然而在解析XML和JSON時(shí)仍然會(huì)有大量的內(nèi)存開銷,甚至有10~100MB之多,可我們很難杜絕XML和JSON的使用。但是請(qǐng)記?。篨ML和JSON會(huì)帶來很大的內(nèi)存開銷。
如果應(yīng)用的內(nèi)存占用不斷提升,你就要開始對(duì)其進(jìn)行GC調(diào)優(yōu)了。我把GC調(diào)優(yōu)的目標(biāo)分為以下兩類:
降低移動(dòng)到老年代的對(duì)象數(shù)量
縮短Full GC的執(zhí)行時(shí)間
降低移動(dòng)到老年代的對(duì)象數(shù)量在Oracle JVM中除了JDK 7及最高版本中引入的G1 GC外,其他的GC都是基于分代回收的。也就是對(duì)象會(huì)在Eden區(qū)中創(chuàng)建,然后不斷在Survivor中來回移動(dòng)。之后如果該對(duì)象依然存活,就會(huì)被移到老年代中。有些對(duì)象,因?yàn)檎加每臻g太大以致于在Eden區(qū)中創(chuàng)建后就直接移動(dòng)到了老年代。老年代的GC較新生代會(huì)耗時(shí)更長(zhǎng),因此減少移動(dòng)到老年代的對(duì)象數(shù)量可以降低full GC的頻率。減少對(duì)象轉(zhuǎn)移到老年代可能會(huì)被誤解為把對(duì)象保留在新生代,然而這是不可能的,相反你可以調(diào)整新生代的空間大小。
縮短Full GC耗時(shí)Full GC的單次執(zhí)行與Minor GC相比,耗時(shí)有較明顯的增加。如果執(zhí)行Full GC占用太長(zhǎng)時(shí)間(例如超過1秒),在對(duì)外服務(wù)的連接中就可能會(huì)出現(xiàn)超時(shí)。
如果企圖通過縮小老年代空間的方式來降低Full GC執(zhí)行時(shí)間,可能會(huì)面臨OutOfMemoryError或者帶來更頻繁的Full GC。
如果通過增加老年代空間來減少Full GC執(zhí)行次數(shù),單次Full GC耗時(shí)將會(huì)增加。
因此,需要為老年代空間設(shè)置適當(dāng)?shù)拇笮?/strong>。
影響GC性能的選項(xiàng)在理解Java垃圾回收的結(jié)尾,我說過不要有這樣的想法:別人通過某個(gè)GC選項(xiàng)獲得了明顯的性能提升,為什么我不直接用這個(gè)選項(xiàng)呢。因?yàn)?strong>不同的服務(wù)所擁有的對(duì)象數(shù)量和對(duì)象的生命周期是不同的。
一個(gè)簡(jiǎn)單場(chǎng)景,如果執(zhí)行一個(gè)任務(wù)需要五個(gè)條件:A, B, C, D和E,另外一個(gè)任務(wù)只需要兩個(gè)條件A和B,哪個(gè)任務(wù)會(huì)快一些?通常只需要條件A和B的任務(wù)會(huì)快一些。
Java GC選項(xiàng)的設(shè)置也是一樣的道理。設(shè)置很多選項(xiàng)未必能提高GC執(zhí)行速度,相反還可能會(huì)更加耗時(shí)。GC調(diào)優(yōu)的基本規(guī)則是對(duì)兩臺(tái)或更多的服務(wù)器設(shè)置不同的選項(xiàng),并對(duì)比性能表現(xiàn),然后把被證明能提升性能的選項(xiàng)添加到應(yīng)用服務(wù)器上。請(qǐng)記住這一點(diǎn)。
下表列出了與內(nèi)存相關(guān)的且會(huì)影響性能的GC選項(xiàng):
表1: GC調(diào)優(yōu)需要關(guān)注的選項(xiàng)
分類 | 選項(xiàng) | 說明 |
---|---|---|
堆空間 | -Xms | 啟動(dòng)JVM時(shí)的初始堆空間大小 |
-Xmx | 堆空間最大值 | |
新生代空間 | -XX:NewRatio | 新生代與老年代的比例 |
-XX:NewSize | 新生代大小 | |
-XX:SurvivorRatio | Eden區(qū)與Survivor區(qū)的比例 |
我經(jīng)常會(huì)使用的選項(xiàng)是:-Xms, -Xmx 和 -XX:NewRatio,其中-Xms和-Xmx是必須的。而如何設(shè)置-XX:NewRatio對(duì)性能會(huì)有顯著的影響。
可能有人會(huì)問如何設(shè)置永久代(Perm)的大小, 可以使用-XX:PermSize和-XX:MaxPermSize進(jìn)行設(shè)置,但記住只有發(fā)生由Perm空間不足導(dǎo)致的OutOfMemoryError時(shí)才需要設(shè)置。
另外一個(gè)會(huì)影響GC性能的選項(xiàng)是GC類型,下表列出了JDK 6.0中能使用的相關(guān)設(shè)置選項(xiàng):
表2: GC類型選項(xiàng)
分類 | 選項(xiàng) | 說明 | |
---|---|---|---|
Serial GC | -XX:+UseSerialGC | ||
Parallel GC | -XX:+UseParallelGC -XX:ParallelGCThreads= |
||
Parallel Compacting GC | -XX:+UseParallelOldGC | ||
CMS GC | -XX:+UseConcMarkSweepGC -XX:UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction= -XX:+UseCMSInitiatingOccupancyOnly |
||
G1 | -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC |
在JDK6中使用G1時(shí),這兩個(gè)選項(xiàng)必須同時(shí)設(shè)置 |
除了G1,其他GC類型都是通過每個(gè)選行列的第一行選項(xiàng)進(jìn)行設(shè)置。通常最不會(huì)使用的是Serial GC,它是為client應(yīng)用優(yōu)化和設(shè)計(jì)的。
還有很多其他影響GC性能的選項(xiàng),但不如上面這些對(duì)性能的影響明顯。另外設(shè)置更多選項(xiàng)未必能優(yōu)化GC的執(zhí)行時(shí)間。
GC調(diào)優(yōu)過程GC調(diào)優(yōu)過程與一般的性能改進(jìn)流程很相似,下面會(huì)介紹我在GC調(diào)優(yōu)過程中的流程。
1. 監(jiān)控GC狀態(tài)首先需要監(jiān)控GC狀態(tài)信息以明確在GC操作過程中對(duì)系統(tǒng)的影響。具體方式可以回顧上一篇文章:Java 垃圾回收的監(jiān)控。
2. 分析監(jiān)控?cái)?shù)據(jù)并決定是否需要GC調(diào)優(yōu)然后通過GC操作狀態(tài),對(duì)監(jiān)控結(jié)果進(jìn)行分析,并判斷是否有必要進(jìn)行GC調(diào)優(yōu)。如果分析結(jié)果顯示GC耗時(shí)在0.1-0.3秒以內(nèi)的話,一般不需要花費(fèi)額外的時(shí)間做GC調(diào)優(yōu)。然而,如果GC耗時(shí)達(dá)到1-3秒甚至10秒以上,就需要立即對(duì)系統(tǒng)進(jìn)行GC調(diào)優(yōu)。
但是如果你的應(yīng)用分配了10GB的內(nèi)存,且不能降低內(nèi)存容量的話,其實(shí)是沒辦法進(jìn)行GC調(diào)優(yōu)的。這種情況下,你首先要去思考為什么需要分配這么大的內(nèi)存。如果只給應(yīng)用分配了1GB或者2GB內(nèi)存,當(dāng)有OutOfMemeoryError發(fā)生時(shí),你需要通過堆dump來分析驗(yàn)證內(nèi)存溢出的原因并進(jìn)行修復(fù)。
3. 設(shè)置GC類型和內(nèi)存大小注釋:
堆dump是把內(nèi)存情況按一定格式輸出到文件,可用于檢查Java 內(nèi)存中的對(duì)象和數(shù)據(jù)情況??墒褂肑DK中內(nèi)置的jmap命令創(chuàng)建堆dump文件。創(chuàng)建文件過程中,Java進(jìn)程會(huì)中斷,因此不要在正常運(yùn)行時(shí)系統(tǒng)上做此操作。
如果決定做GC調(diào)優(yōu),就需要考慮如何選擇GC類型、如何設(shè)置內(nèi)存大小。如果你有多臺(tái)服務(wù)器,可通過為每臺(tái)服務(wù)器設(shè)置不同的GC選項(xiàng)并對(duì)比不同的表現(xiàn),這一步很重要。
4. 分析GC調(diào)優(yōu)結(jié)果設(shè)置GC選項(xiàng)后,至少要收集24小時(shí)的GC表現(xiàn)數(shù)據(jù),然后就可以著手分析這些數(shù)據(jù)了。如果足夠幸運(yùn),通過分析就剛好找到了最合適的GC選項(xiàng)。否則就需要分析GC日志,并分析內(nèi)存的分配情況。然后通過不同的調(diào)整GC類型和內(nèi)存大小來找到系統(tǒng)的最優(yōu)選項(xiàng)。
5. 如果結(jié)果可接受,則對(duì)所有服務(wù)應(yīng)用調(diào)優(yōu)選項(xiàng)并停止調(diào)優(yōu)如果GC結(jié)果令人滿意,就可以把相應(yīng)的選項(xiàng)應(yīng)用到所有服務(wù)器并停止GC調(diào)優(yōu)。
下面的章節(jié)會(huì)詳細(xì)介紹每個(gè)步驟中的詳細(xì)過程。
監(jiān)控GC狀態(tài)并分析GC結(jié)果監(jiān)控Web應(yīng)用(WAS: Web Application Server)GC運(yùn)行狀態(tài)的最好方式是使用jstat命令。在Java 垃圾回收的監(jiān)控部分已經(jīng)介紹了如何使用jstat命令,所以這里就直接介紹怎么樣來校驗(yàn)結(jié)果數(shù)據(jù)。
下面的例子中列出了JVM未做GC調(diào)優(yōu)時(shí)的數(shù)據(jù):
$ jstat -gcutil 21719 1s S0 S1 E O P YGC YGCT FGC FGCT GCT 48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673 48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673
看一下表中的YGC和YGCT,YGCT 除以 YGC算出平均單次YGC耗時(shí)為0.05秒。也就是說在新生代執(zhí)行一次垃圾回收的平均耗時(shí)為50毫秒。通過這份結(jié)果,我們可以無須關(guān)注新生代的垃圾回收。
然后再看一下FGCT和FGC,F(xiàn)GCT除以FGC算出平均單次FGC耗時(shí)為19.68秒。也就是平均需要消耗19.68秒來執(zhí)行一次Full GC。上面的結(jié)果(共3次Full GC)可能是每次Full GC都耗時(shí)19.68秒,也有可能是其中兩次都只耗時(shí)1秒,而另外一次卻消耗了58秒。然而不管哪種情況,都迫切需要進(jìn)行GC調(diào)優(yōu)。
當(dāng)然也可以通過jstat來校驗(yàn)結(jié)果,不過分析GC的最好方式是使用-verbosegc選項(xiàng)來啟動(dòng)JVM。在前面的文章中我已經(jīng)詳細(xì)介紹了生成日志的方式以及如何進(jìn)行分析。就分析-verbosegc日志而言,HPJMeter是我最偏愛的工具,因?yàn)樗?jiǎn)單易用。使用HPJMeter可以輕松獲取GC執(zhí)行時(shí)間的開銷以及GC發(fā)生的頻率。
如果GC執(zhí)行時(shí)間滿足以下判斷條件,那么GC調(diào)優(yōu)并沒那么必須。
Minor GC執(zhí)行迅速(50毫秒以內(nèi))
Minor GC執(zhí)行不頻繁(間隔10秒左右一次)
Full GC執(zhí)行迅速(1秒以內(nèi))
Full GC執(zhí)行不頻繁(間隔10分鐘左右一次)
括號(hào)內(nèi)的值并非絕對(duì),依據(jù)應(yīng)用的服務(wù)狀態(tài)會(huì)有不同。有些服務(wù)可能要求Full GC處理速度不能超過0.9秒,另外一些服務(wù)可能會(huì)寬松些。因此校驗(yàn)GC結(jié)果并根據(jù)具體的服務(wù)需要,決定是否要進(jìn)行GC調(diào)優(yōu)。
在校驗(yàn)GC狀態(tài)時(shí),不要只關(guān)心Minor GC和Full GC的耗時(shí),也要GC執(zhí)行次數(shù)也同樣重要。如果新生代太小,Minor GC就會(huì)頻繁執(zhí)行(甚至每間隔1秒就要執(zhí)行一次)。另外,新生代太小導(dǎo)致轉(zhuǎn)移到老年代的對(duì)象增多,也會(huì)引起Full GC的頻繁執(zhí)行。因此使用`-gccapacity`配合jstat命令,以檢查內(nèi)存空間的使用情況。
設(shè)置GC類型和內(nèi)存大小 設(shè)置GC類型Oracle JVM提供了5種GC類型,如果是低于JDK 7的版本,可以使用Parallel GC, Parallel Compacting GC, CMS GC。當(dāng)然,到底選哪一個(gè)并沒有統(tǒng)一的準(zhǔn)則或標(biāo)準(zhǔn)。
所以如何選擇合適的GC類型?推薦方案是將這三種GC都應(yīng)用到應(yīng)用中進(jìn)行對(duì)比。不過可以明確的是CMS GC肯定比Parallel GCs更快,即然這樣只使用CMS GC便好。然而CMS GC也有出問題的時(shí)候,通常Full GC中使用CMS GC會(huì)執(zhí)行更快,如果CMS GC的并發(fā)模式失敗,則會(huì)出現(xiàn)比Parallel GCs慢的情況。
并發(fā)模式失敗我們來深入看一下并發(fā)模式失敗的場(chǎng)景。
Parallel GC與CMS GC最大的區(qū)別在于壓縮任務(wù)。壓縮任務(wù)通過壓縮內(nèi)存使用來移除內(nèi)存中的碎片空間,以清理兩塊已分配使用的內(nèi)存空間中的間隙。
在Parallel GC中,只要執(zhí)行Full GC便會(huì)進(jìn)行內(nèi)存壓縮,因此耗時(shí)更長(zhǎng)。不過Full GC之后,因?yàn)閴嚎s的原故,可以分配連續(xù)的空間,所以內(nèi)存的分配速度為更快一些。
與之相反,CMS GC的執(zhí)行中并不會(huì)伴隨內(nèi)存壓縮,因此GC速度會(huì)更快一些。然而,因?yàn)槲醋鰞?nèi)存壓縮, GC清理過程中釋放的內(nèi)存便會(huì)成為空閑空間。因?yàn)榭臻g不連續(xù),可能會(huì)導(dǎo)致在創(chuàng)建大對(duì)象時(shí)空間不足。例如,如果老年代尚有300M空閑,卻不能為10MB的對(duì)象分配足夠的連續(xù)空間。這時(shí)便會(huì)發(fā)生并發(fā)模式失敗的警告,并觸發(fā)內(nèi)存壓縮。如果使用CMS GC,在內(nèi)存壓縮過程中可能會(huì)比Parallel GCs更為耗時(shí),也可能會(huì)帶來其他問題。關(guān)于"并發(fā)模式失敗"更詳細(xì)的介紹可以看Oracle 工程師的文章:理解CMS GC 日志。
結(jié)論就是,要為你的系統(tǒng)尋找合適的GC類型。
每個(gè)系統(tǒng)都有一個(gè)最適當(dāng)?shù)腉C類型,所以你需要找到這個(gè)GC類型。如果你有6臺(tái)服務(wù)器,建議你為每?jī)山M設(shè)置相同的選項(xiàng),并通過-verbosegc選項(xiàng)對(duì)結(jié)果進(jìn)行分析和比較。
調(diào)整內(nèi)存大小下面先列出內(nèi)存大小與GC執(zhí)行次數(shù)、每次GC耗時(shí)之間的關(guān)系:
大內(nèi)存
會(huì)降低GC執(zhí)行次數(shù)
相應(yīng)的會(huì)增加GC執(zhí)行耗時(shí)
小內(nèi)存
會(huì)縮知單次GC耗時(shí)
相應(yīng)的會(huì)增加GC執(zhí)行次數(shù)
當(dāng)然,關(guān)于使用大內(nèi)存還是小內(nèi)存并沒有唯一正確的答案。如果服務(wù)器資源足夠且Full GC執(zhí)行耗時(shí)能控制在1秒以內(nèi),使用10GB的內(nèi)存也是可以的。但大多數(shù)時(shí)候如果設(shè)置內(nèi)存為10GB,GC執(zhí)行效果并不盡人意,執(zhí)行一次Full GC可能要消耗10~30秒(具體時(shí)長(zhǎng)也會(huì)根據(jù)對(duì)象大小情況而不同)。
既然如此,如何正確設(shè)置內(nèi)存大小。通常情況下,我會(huì)推薦500MB大小。這不是說你要把自己的WAS(Web Application Server)內(nèi)存選項(xiàng)設(shè)置為-Xms500和-Xmx500m?;诋?dāng)前未調(diào)優(yōu)時(shí)的場(chǎng)景,檢查Full GC之后內(nèi)存大小變化。如果Full GC之后尚有300MB空間剩余,這樣最好把內(nèi)存設(shè)置到1GB(300MB(默認(rèn)使用) + 500MB(老年代最小容量) + 200MB(空閑空間))。這意味著你應(yīng)該才老年代至少設(shè)置500MB空間。如果你有3臺(tái)服務(wù)器,可以分別設(shè)置1GB、1.5GB和2GB,并檢查每臺(tái)機(jī)器的執(zhí)行結(jié)果。
理論上,根據(jù)內(nèi)存大小不同單次執(zhí)行GC速度應(yīng)該是1GB > 1.5GB > 2GB,所以1GB的內(nèi)存會(huì)中三個(gè)之中GC速度最快的。但并不能保證1GB的內(nèi)存Full GC耗時(shí)1秒,2GB的內(nèi)存Full GC耗時(shí)2秒。實(shí)際耗時(shí)與機(jī)器性能和對(duì)象大小也有關(guān)系。所以最好的度量方式是設(shè)置每種可能性并分析他們的監(jiān)控結(jié)果。
有設(shè)置內(nèi)存大小時(shí),還需要設(shè)置另外一選項(xiàng):NewRatio。NewRatio是新生代與老年代的比值的倒數(shù)(即老年代與新生代的比值)。如果XX:NewRatio=1,就是說新生代 : 老年代的比值為1:1。對(duì)于1GB內(nèi)存,就是新生代與老年代各500MB。如果NewRatio的值是2,則是新生代 : 老年代的值為1:2。因此比值設(shè)置的越大,老年代的空間就越大,相應(yīng)的新生代空間會(huì)越小。
設(shè)置NewRatio也不是一件重要的事,但可能會(huì)對(duì)整個(gè)GC性能帶來嚴(yán)重影響。如果新生代太小,對(duì)象就會(huì)轉(zhuǎn)移到老年代,引起頻繁的Full GC,導(dǎo)致更多的耗時(shí)。
你可能簡(jiǎn)單的認(rèn)為設(shè)置NewRatio=1會(huì)帶來最佳的效果,然而并非如此。把NewRatio設(shè)置為2或3更容易帶來好的GC表現(xiàn)。當(dāng)然我也實(shí)際遇到過一些這樣的例子。
完成GC調(diào)優(yōu)的最快途徑是什么?通過對(duì)比性能測(cè)試的結(jié)果是得到GC調(diào)優(yōu)結(jié)果的最快途徑。通過為每個(gè)服務(wù)器設(shè)置不同的選項(xiàng)并觀察GC狀態(tài),最好能觀察1到2天的數(shù)據(jù)。如果是通過性能測(cè)試來做GC調(diào)優(yōu)的話,要為每個(gè)服務(wù)器準(zhǔn)備相同的負(fù)載和業(yè)務(wù)操作。請(qǐng)求比例的分配也要與業(yè)務(wù)條件相一致。然而即便是專業(yè)的性能測(cè)試人員,準(zhǔn)備精確的負(fù)載數(shù)據(jù)也并非易事,通常需要花費(fèi)很大精力來做準(zhǔn)備。所以更簡(jiǎn)捷的GC調(diào)優(yōu)方式就是對(duì)業(yè)務(wù)應(yīng)用準(zhǔn)備GC選項(xiàng),然后通過等待GC結(jié)果并進(jìn)行分析,盡管可能需要更長(zhǎng)的等待時(shí)間。
分析GC調(diào)優(yōu)結(jié)果在應(yīng)用GC選項(xiàng)并設(shè)置-verbosegc后,可以通過tail命令檢查日志是否按期望的方式正常輸出。如果選項(xiàng)未精確的設(shè)置或者沒有按期望輸出,你所花費(fèi)的時(shí)間都將白費(fèi)。如果日志輸出與期望相符,等待1到2天的運(yùn)行后便可檢查和分析結(jié)果。最簡(jiǎn)單的方式是把日志文件復(fù)制到本地PC,并使用HPJMeter進(jìn)行分析。
分析過程中主要關(guān)注以下數(shù)據(jù),下面列表是按我自己定義的優(yōu)先級(jí)列出的。其中決定GC選項(xiàng)的最重要的數(shù)據(jù)是Full GC執(zhí)行時(shí)間。
Full GC(平均)耗時(shí)
Minor GC(平均)耗時(shí)
Full GC執(zhí)行間隔
MinorGC執(zhí)行間隔
Full GC整體耗時(shí)
Minor GC整體耗時(shí)
GC整體耗時(shí)
Full GC執(zhí)行次數(shù)
Minor GC執(zhí)行次數(shù)
如果足夠幸運(yùn),你能恰好找到合適的GC選項(xiàng),通常你并沒這么幸運(yùn)。執(zhí)行GC調(diào)優(yōu)時(shí)一定要格外小心,因?yàn)槿绻阍噲D一次就完成GC調(diào)優(yōu),得到的可能會(huì)是OutOfMemoryError。
調(diào)優(yōu)案例上面我們對(duì)于GC調(diào)優(yōu)的討論還僅是紙上談兵,現(xiàn)在開始我們看一些具體的GC調(diào)優(yōu)的案例。
案例1這個(gè)例子是為服務(wù)S進(jìn)行的GC優(yōu)化。對(duì)于這個(gè)新上線的服務(wù)S,在執(zhí)行Full GC時(shí)有些過于耗時(shí)。
先看一下jstat -gcutil的結(jié)果:
S0 S1 E O P YGC YGCT FGC FGCT GCT 12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993
在開始進(jìn)行調(diào)優(yōu)時(shí)不用太關(guān)心持久代空間的設(shè)置,相對(duì)而言YGC的數(shù)值更值得關(guān)注。
從上面的結(jié)果中我們可算出執(zhí)行Minor GC和Full GC的平均時(shí)間上的開銷,如下表:
表3:服務(wù)S執(zhí)行Minor GC和Full GC的平均耗時(shí)
GC類型 | GC 執(zhí)行次數(shù) | GC執(zhí)行時(shí)間 | 平均耗時(shí) |
---|---|---|---|
Minor GC | 54 | 2.047 | 37 ms |
Full GC | 5 | 6.946 | 1389 ms |
對(duì)于Minor GC來說,37 ms還不算壞,而Full GC的平均耗時(shí)1.389 s對(duì)于系統(tǒng)來說在執(zhí)行Full GC時(shí)可能會(huì)導(dǎo)致頻繁的超時(shí)現(xiàn)象,例如DB超時(shí)設(shè)置為1 s的話就會(huì)發(fā)生超時(shí)。所以這個(gè)案例中的系統(tǒng)需要進(jìn)行GC調(diào)優(yōu)。
首先在開始GC調(diào)優(yōu)之前先檢查當(dāng)前的內(nèi)存設(shè)置??梢允褂?b>jstat -gccapacity選項(xiàng)查看內(nèi)存的使用情況。下面是服務(wù)S的檢查結(jié)果:
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC 212992.0 212992.0 212992.0 21248.0 21248.0 170496.0 1884160.0 1884160.0 1884160.0 1884160.0 262144.0 262144.0 262144.0 262144.0 54 5
其中關(guān)鍵的數(shù)據(jù)如下:
新生代使用:212, 992 KB(約208 MB)
老年代使用:1,884,160 KB(約1.8 GB)
所以除去持久代之外的內(nèi)存分配為2 GB,且新生代 : 老年代為 1:9 (即NewRatio=9)。為了看到更詳細(xì)的信息,對(duì)系統(tǒng)的三個(gè)不同實(shí)現(xiàn)均設(shè)置了-verbosegc并分別設(shè)置了NewRatio選項(xiàng),除此之外未添加其他選項(xiàng)。
NewRatio = 2
NewRatio = 3
NewRatio = 4
一天之后檢查GC時(shí)日志時(shí)幸運(yùn)的發(fā)生,在設(shè)置NewRatio之后尚未有Full GC發(fā)生。
發(fā)生了什么?因?yàn)榇蠖鄶?shù)對(duì)象在創(chuàng)建之后不久就被銷毀,所以新生代里的對(duì)象在移到老年代之前就被銷毀掉了。
既然如此,就沒必要再設(shè)置其他選項(xiàng),只是選擇好最佳的NewRatio即可。如何選取最佳NewRatio?只能逐個(gè)分析設(shè)置不同NewRatio值時(shí)的Minor GC的平均耗時(shí)。
上面三個(gè)NewRatio設(shè)置對(duì)應(yīng)的Minor GC平均耗時(shí)如下:
NewRatio=2: 45ms
NewRatio=3: 34ms
NewRatio=4: 30ms
因?yàn)?b>NewRatio=4時(shí)Minor GC具有最小的耗時(shí),所以就是我們選擇的最佳設(shè)置,即便此時(shí)新生代的空間相對(duì)較小。應(yīng)用此選項(xiàng)后,服務(wù)再也沒有Full GC發(fā)生。
下面是系統(tǒng)重新設(shè)置過選項(xiàng)后,某天通過jstat -gcutil查看到的結(jié)果:
S0 S1 E O P YGC YGCT FGC FGCT GCT 8.61 0.00 30.67 24.62 22.38 2424 30.219 0 0.000 30.219
你可能認(rèn)為因?yàn)橄到y(tǒng)接收的請(qǐng)求太少以致于GC發(fā)生頻率較低,然而在Minor GC執(zhí)行了2,424次的情況下系統(tǒng)未發(fā)生Full GC。
案例2下面介紹的是服務(wù)A的例子。我們?cè)诠镜膽?yīng)用性能管理平臺(tái)(APM: Application Performance Manager)上發(fā)現(xiàn)服務(wù)A的JVM周期性的出現(xiàn)長(zhǎng)時(shí)間的停頓(超過8秒未有響應(yīng))的現(xiàn)象。所以我們決定對(duì)其進(jìn)行GC調(diào)優(yōu)。經(jīng)過排查我們發(fā)現(xiàn)此系統(tǒng)在執(zhí)行Full GC時(shí)太過耗時(shí),需要進(jìn)行優(yōu)化。
在著手優(yōu)化之前,我們?yōu)橄到y(tǒng)加上了-verbosegc選項(xiàng),輸出結(jié)果如下圖:
圖1:GC調(diào)優(yōu)之前的GC耗時(shí)
上圖是HPJMeter自動(dòng)分析結(jié)果后提供的系統(tǒng)GC隨著JVM運(yùn)行的耗時(shí)圖。X-軸是JVM從啟動(dòng)后的運(yùn)行時(shí)間軸,Y-軸是每次GC的響應(yīng)時(shí)間。其中綠色的是Full GC使用的CMS垃圾回收的耗時(shí),藍(lán)色的是Minor GC使用的Parallel Scavenge垃圾回收的耗時(shí)。
前面我說過CMS GC是最快的,但上圖可看到有場(chǎng)景耗時(shí)竟達(dá)到15秒之多。什么原因?qū)е逻@種后果?回想一下我前面說過的:當(dāng)內(nèi)存壓縮時(shí)CMS將會(huì)變慢。另外服務(wù)A設(shè)置了-Xms1g和-Xmx4g的選項(xiàng),操作系統(tǒng)為其分配的內(nèi)存為4 GB。
然后我把GC類型由GMS換成了Parallel GC,并把內(nèi)存大小設(shè)置為2G,NewRatio設(shè)置為3。一段時(shí)間之后通過jstat -gcutil查看到的結(jié)果如下:
S0 S1 E O P YGC YGCT FGC FGCT GCT 0.00 30.48 3.31 26.54 37.01 226 11.131 4 11.758 22.890
Full GC的速度提升了,與4GB內(nèi)存時(shí)的15秒相比,現(xiàn)在平均每次只需要3秒。但3秒仍然不盡人意,所以我設(shè)計(jì)了以下六組選項(xiàng):
-XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=2
-XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=3
-XX:+UseParallelGC -Xms1g -Xmx1g -XX:NewRatio=3
-XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=2
-XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=3
-XX:+UseParallelOldGC -Xms1g -Xmx1g -XX:NewRatio=3
哪一個(gè)會(huì)更快呢?結(jié)果顯示內(nèi)存越小,速度越快。下圖是第六組選項(xiàng)的GC持續(xù)時(shí)長(zhǎng)分布圖,代表了最優(yōu)的GC性能提升。圖中看到最慢的為1.7秒,而平均值降低到1秒以內(nèi)。
圖2:使用第六組選項(xiàng)后的GC耗時(shí)
因此我把服務(wù)A的GC選項(xiàng)調(diào)整為了第六組中的設(shè)置,然而每天夜里卻連續(xù)發(fā)生了OutOfMemoryError。個(gè)中艱辛不再細(xì)說,簡(jiǎn)而言之就是批量的數(shù)據(jù)處理任務(wù)導(dǎo)致了JVM內(nèi)存泄露。到此為止,所有的問題都明了了。
如果只對(duì)GC日志做短時(shí)間的觀察例把GC調(diào)優(yōu)的結(jié)果應(yīng)用到所有服務(wù)器上是一件非常危險(xiǎn)的事情。一定要記住,如果GC調(diào)優(yōu)能夠順利執(zhí)行而無故障只有一條途徑:像分析GC日志一樣分析系統(tǒng)的每一個(gè)服務(wù)操作。
上面通過兩個(gè)GC調(diào)優(yōu)的案例演示了GC調(diào)優(yōu)的具體處理過程。如我所述,案例中的GC選項(xiàng)可以不做調(diào)整的應(yīng)用到那些具有相同CPU、操作系統(tǒng)和 JDK 版本以及執(zhí)行相同功能的服務(wù)上去。然而不要把這些選項(xiàng)應(yīng)用到你的系統(tǒng)上,因?yàn)樗麄兾幢剡m用。
總結(jié)我執(zhí)行GC調(diào)優(yōu)一般基于經(jīng)驗(yàn)而無需通過堆dump后對(duì)內(nèi)存進(jìn)行詳細(xì)的分析,盡管精確的內(nèi)存狀態(tài)可能會(huì)帶來更好的GC調(diào)優(yōu)結(jié)果。在一般情景,如果內(nèi)存負(fù)載較低時(shí),通過分析內(nèi)存對(duì)象可能效果更好,不過如果服務(wù)負(fù)載較高,內(nèi)存空間使用較多時(shí),更推薦基于經(jīng)驗(yàn)來做GC調(diào)優(yōu)。
我曾經(jīng)在一些服務(wù)上對(duì)G1 GC做過性能測(cè)試,不過還沒有全面使用。結(jié)果證明G1 GC執(zhí)行速度比其他任何GC都要快,不過需要把JDK升級(jí)到 JDK 7 才能享受到G1帶來的性能提升,另外G1的穩(wěn)定性目前尚不能完全保證,沒有人知道是否會(huì)帶來嚴(yán)重的bug。所以大范圍使用 G1 還尚待時(shí)日。
當(dāng) JDK 7 穩(wěn)定以后(并不是說它當(dāng)前不穩(wěn)定),并且WAS針對(duì)JDK 7做過優(yōu)化之后,G1也許會(huì)穩(wěn)定的運(yùn)行在服務(wù)器上,到那時(shí)也許就不再需要進(jìn)行GC調(diào)優(yōu)了。
更多GC調(diào)優(yōu)的細(xì)節(jié)可以在Slideshare上搜索相關(guān)材料。我最推薦的是Twitter 工程師 Attila Szegedi寫的這篇我在Twitter學(xué)到的關(guān)于JVM調(diào)優(yōu)的一切,有時(shí)間可以學(xué)習(xí)一下。
作者:Sangmin Lee, 性能實(shí)驗(yàn)室高級(jí)工程師,NHN公司
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/65377.html
摘要:本文是成為專家系列的第一篇。然而,在多線程環(huán)境下,將會(huì)有別樣的狀況。在中正是通過解決了多線程問題。在最后的并發(fā)清理階段,垃圾回收過程被真正執(zhí)行。在垃圾回收?qǐng)?zhí)行過程中,其他線程依然在執(zhí)行。 原文鏈接:http://www.cubrid.org/blog/de... 了解Java的垃圾回收(GC)原理能給我們帶來什么好處?對(duì)于軟件工程師來說,滿足技術(shù)好奇心可算是一個(gè),但重要的是理解GC能幫...
摘要:調(diào)優(yōu)調(diào)優(yōu)中基于真實(shí)案例介紹了可用于調(diào)優(yōu)的最佳選項(xiàng)。的設(shè)置及其對(duì)的影響的設(shè)置及其對(duì)的影響中介紹了對(duì)選項(xiàng)在系統(tǒng)發(fā)生時(shí)對(duì)整體性能的影響。具體來說,我會(huì)介紹性能優(yōu)化的必要條件判斷是否需要優(yōu)化的步驟,同時(shí)也會(huì)列出在性能優(yōu)化過程中經(jīng)遇到的一些問題。 1. 理解Java垃圾回收 理解Java垃圾回收中我們學(xué)習(xí)了幾種不同的GC算法的處理過程,GC的工作方式,新生代與老年代的區(qū)別。所以,你應(yīng)該已經(jīng)了解...
摘要:原文鏈接這是專家系列文章的第二篇。運(yùn)行在本地虛擬機(jī)上的應(yīng)用的又稱為,通常與相同。性能數(shù)據(jù)需要持續(xù)觀察,因此在運(yùn)行時(shí)需要定時(shí)輸出的監(jiān)控信息。新生代容量的統(tǒng)計(jì)信息。是提供的一個(gè)式的圖表監(jiān)控工具。 原文鏈接:http://www.cubrid.org/blog/dev-platform/how-to-monitor-java-garbage-collection/ 這是GC專家系列文章的第二...
摘要:本文將介紹的參數(shù)的重要性以及在發(fā)生時(shí)對(duì)系統(tǒng)整體性能的顯著影響。我們來看下的選項(xiàng)在發(fā)生時(shí)會(huì)對(duì)系統(tǒng)帶來哪些影響。所以這些請(qǐng)求將會(huì)放到堆積隊(duì)列,隊(duì)列的長(zhǎng)度是的中設(shè)置的。從而導(dǎo)致進(jìn)程的數(shù)量超過,并觸發(fā)了操作系統(tǒng)進(jìn)行內(nèi)存交換的閥值。 原文鏈接:http://www.cubrid.org/blog/dev-platform/maxclients-in-apache-and-its-effect-o...
摘要:在本文中我將會(huì)介紹應(yīng)用性能優(yōu)化的一般原則。性能優(yōu)化的流程圖摘取自和合著的性能,描述了應(yīng)用性能優(yōu)化的處理流程。例如,對(duì)每臺(tái)服務(wù)器,你面臨著為單個(gè)分配堆內(nèi)存和運(yùn)行個(gè)并為每個(gè)分配堆內(nèi)存的選擇。不過位能使用堆內(nèi)存最大理論值只有。 原文鏈接:http://www.cubrid.org/blog/dev-platform/the-principles-of-java-application-per...
閱讀 3020·2023-04-25 19:20
閱讀 883·2021-11-24 09:38
閱讀 2164·2021-09-26 09:55
閱讀 2517·2021-09-02 15:11
閱讀 2272·2019-08-30 15:55
閱讀 3675·2019-08-30 15:54
閱讀 3227·2019-08-30 14:03
閱讀 3028·2019-08-29 17:11