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