jvm系列(九):如何優(yōu)化Java GC「譯」

      網(wǎng)友投稿 878 2025-04-01

      本文由CrowHawk(https://crowhawk.github.io/2017/08/21/jvm_4/)翻譯,是Java GC調(diào)優(yōu)的經(jīng)典佳作。

      本文是“成為Java GC專家”系列文章的第三篇,在系列的第一篇文章《理解Java GC》中,我們了解到了不同GC算法的執(zhí)行過程、GC的工作原理、新生代和老年代的概念、JDK 7中你需要了解的5種GC類型以及每一種GC對性能的影響。

      在系列的第二篇文章《如何監(jiān)控Java GC》中筆者已經(jīng)解釋了JVM進(jìn)行實時GC的原理、監(jiān)控GC的方法以及可以使這一過程更加迅速高效的工具。

      在第三篇文章中,筆者將基于實際生產(chǎn)環(huán)境中的案例,介紹幾個GC優(yōu)化的最佳參數(shù)設(shè)置。在此我們假設(shè)你已經(jīng)理解了本系列前兩篇文章的內(nèi)容,因此為了更深入的理解本文所講內(nèi)容,我建議你在閱讀本篇文章之前先仔細(xì)閱讀這兩篇文章。

      GC優(yōu)化是必要的嗎?

      或者更準(zhǔn)確地說,GC優(yōu)化對Java基礎(chǔ)服務(wù)來說是必要的嗎?答案是否定的,事實上GC優(yōu)化對Java基礎(chǔ)服務(wù)來說在有些場合是可以省去的,但前提是這些正在運行的Java系統(tǒng),必須包含以下參數(shù)或行為:

      內(nèi)存大小已經(jīng)通過-Xms和-Xmx參數(shù)指定過

      運行在server模式下(使用-server參數(shù))

      系統(tǒng)中沒有殘留超時日志之類的錯誤日志

      換句話說,如果你在運行時沒有手動設(shè)置內(nèi)存大小并且打印出了過多的超時日志,那你就需要對系統(tǒng)進(jìn)行GC優(yōu)化。

      不過你需要時刻謹(jǐn)記一句話:GC tuning is the last task to be done.

      現(xiàn)在來想一想GC優(yōu)化的最根本原因,垃圾收集器的工作就是清除Java創(chuàng)建的對象,垃圾收集器需要清理的對象數(shù)量以及要執(zhí)行的GC數(shù)量均取決于已創(chuàng)建的對象數(shù)量。因此,為了使你的系統(tǒng)在GC上表現(xiàn)良好,首先需要減少創(chuàng)建對象的數(shù)量。

      俗話說“冰凍三尺非一日之寒”,我們在編碼時要首先要把下面這些小細(xì)節(jié)做好,否則一些瑣碎的不良代碼累積起來將讓GC的工作變得繁重而難于管理:

      使用?StringBuilder或?StringBuffer來代替?String

      盡量少輸出日志

      盡管如此,仍然會有我們束手無策的情況。XML和JSON解析過程往往占用了最多的內(nèi)存,即使我們已經(jīng)盡可能地少用String、少輸出日志,仍然會有大量的臨時內(nèi)存(大約10-100MB)被用來解析XML或JSON文件,但我們又很難棄用XML和JSON。在此,你只需要知道這一過程會占據(jù)大量內(nèi)存即可。

      如果在經(jīng)過幾次重復(fù)的優(yōu)化后應(yīng)用程序的內(nèi)存用量情況有所改善,那么久可以啟動GC優(yōu)化了。

      筆者總結(jié)了GC優(yōu)化的兩個目的:

      將進(jìn)入老年代的對象數(shù)量降到最低

      減少Full GC的執(zhí)行時間

      將進(jìn)入老年代的對象數(shù)量降到最低

      除了可以在JDK 7及更高版本中使用的G1收集器以外,其他分代GC都是由Oracle JVM提供的。關(guān)于分代GC,就是對象在Eden區(qū)被創(chuàng)建,隨后被轉(zhuǎn)移到Survivor區(qū),在此之后剩余的對象會被轉(zhuǎn)入老年代。也有一些對象由于占用內(nèi)存過大,在Eden區(qū)被創(chuàng)建后會直接被傳入老年代。老年代GC相對來說會比新生代GC更耗時,因此,減少進(jìn)入老年代的對象數(shù)量可以顯著降低Full GC的頻率。你可能會以為減少進(jìn)入老年代的對象數(shù)量意味著把它們留在新生代,事實正好相反,新生代內(nèi)存的大小是可以調(diào)節(jié)的。

      降低Full GC的時間

      Full GC的執(zhí)行時間比Minor GC要長很多,因此,如果在Full GC上花費過多的時間(超過1s),將可能出現(xiàn)超時錯誤。

      如果通過減小老年代內(nèi)存來減少Full GC時間,可能會引起?OutOfMemoryError或者導(dǎo)致Full GC的頻率升高。

      另外,如果通過增加老年代內(nèi)存來降低Full GC的頻率,F(xiàn)ull GC的時間可能因此增加。

      因此,你需要把老年代的大小設(shè)置成一個“合適”的值。

      影響GC性能的參數(shù)

      正如我在系列的第一篇文章《理解Java GC》末尾提到的,不要幻想著“如果有人用他設(shè)置的GC參數(shù)獲取了不錯的性能,我們?yōu)槭裁床粡?fù)制他的參數(shù)設(shè)置呢?”,因為對于不用的Web服務(wù),它們創(chuàng)建的對象大小和生命周期都不相同。

      舉一個簡單的例子,如果一個任務(wù)的執(zhí)行條件是A,B,C,D和E,另一個完全相同的任務(wù)執(zhí)行條件只有A和B,那么哪一個任務(wù)執(zhí)行速度更快呢?作為常識來講,答案很明顯是后者。

      Java GC參數(shù)的設(shè)置也是這個道理,設(shè)置好幾個參數(shù)并不會提升GC執(zhí)行的速度,反而會使它變得更慢。GC優(yōu)化的基本原則是將不同的GC參數(shù)應(yīng)用到兩個及以上的服務(wù)器上然后比較它們的性能,然后將那些被證明可以提高性能或減少GC執(zhí)行時間的參數(shù)應(yīng)用于最終的工作服務(wù)器上。

      下面這張表展示了與內(nèi)存大小相關(guān)且會影響GC性能的GC參數(shù)

      筆者在進(jìn)行GC優(yōu)化時最常用的參數(shù)是?-Xms,?-Xmx和?-XX:NewRatio。?-Xms和?-Xmx參數(shù)通常是必須的,所以?NewRatio的值將對GC性能產(chǎn)生重要的影響。

      有些人可能會問如何設(shè)置永久代內(nèi)存大小,你可以用?-XX:PermSize和?-XX:MaxPermSize參數(shù)來進(jìn)行設(shè)置,但是要記住,只有當(dāng)出現(xiàn)?OutOfMemoryError錯誤時你才需要去設(shè)置永久代內(nèi)存。

      還有一個會影響GC性能的因素是垃圾收集器的類型,下表展示了關(guān)于GC類型的可選參數(shù)(基于JDK 6.0):

      除了G1收集器外,可以通過設(shè)置上表中每種類型第一行的參數(shù)來切換GC類型,最常見的非侵入式GC就是Serial GC,它針對客戶端系統(tǒng)進(jìn)行了特別的優(yōu)化。

      會影響GC性能的參數(shù)還有很多,但是上述的參數(shù)會帶來最顯著的效果,請切記,設(shè)置太多的參數(shù)并不一定會提升GC的性能。

      GC優(yōu)化的過程

      GC優(yōu)化的過程和大多數(shù)常見的提升性能的過程相似,下面是筆者使用的流程:

      1.監(jiān)控GC狀態(tài)

      你需要監(jiān)控GC從而檢查系統(tǒng)中運行的GC的各種狀態(tài),具體方法請查看系列的第二篇文章《如何監(jiān)控Java GC》

      2.分析監(jiān)控結(jié)果后決定是否需要優(yōu)化GC

      在檢查GC狀態(tài)后,你需要分析監(jiān)控結(jié)構(gòu)并決定是否需要進(jìn)行GC優(yōu)化。如果分析結(jié)果顯示運行GC的時間只有0.1-0.3秒,那么就不需要把時間浪費在GC優(yōu)化上,但如果運行GC的時間達(dá)到1-3秒,甚至大于10秒,那么GC優(yōu)化將是很有必要的。

      但是,如果你已經(jīng)分配了大約10GB內(nèi)存給Java,并且這些內(nèi)存無法省下,那么就無法進(jìn)行GC優(yōu)化了。在進(jìn)行GC優(yōu)化之前,你需要考慮為什么你需要分配這么大的內(nèi)存空間,如果你分配了1GB或2GB大小的內(nèi)存并且出現(xiàn)了?OutOfMemoryError,那你就應(yīng)該執(zhí)行堆轉(zhuǎn)儲(heap dump)來消除導(dǎo)致異常的原因。

      注意:

      堆轉(zhuǎn)儲(heap dump)是一個用來檢查Java內(nèi)存中的對象和數(shù)據(jù)的內(nèi)存文件。該文件可以通過執(zhí)行JDK中的?jmap命令來創(chuàng)建。在創(chuàng)建文件的過程中,所有Java程序都將暫停,因此,不要再系統(tǒng)執(zhí)行過程中創(chuàng)建該文件。

      你可以在互聯(lián)網(wǎng)上搜索heap dump的詳細(xì)說明。對于韓國讀者,可以直接參考我去年發(fā)布的書:《The story of troubleshooting for Java developers and system operators》?(Sangmin Lee, Hanbit Media, 2011, 416 pages)

      3.設(shè)置GC類型/內(nèi)存大小

      如果你決定要進(jìn)行GC優(yōu)化,那么你需要選擇一個GC類型并且為它設(shè)置內(nèi)存大小。此時如果你有多個服務(wù)器,請如上文提到的那樣,在每臺機(jī)器上設(shè)置不同的GC參數(shù)并分析它們的區(qū)別。

      4.分析結(jié)果

      在設(shè)置完GC參數(shù)后就可以開始收集數(shù)據(jù),請在收集至少24小時后再進(jìn)行結(jié)果分析。如果你足夠幸運,你可能會找到系統(tǒng)的最佳GC參數(shù)。如若不然,你還需要分析輸出日志并檢查分配的內(nèi)存,然后需要通過不斷調(diào)整GC類型/內(nèi)存大小來找到系統(tǒng)的最佳參數(shù)。

      5.如果結(jié)果令人滿意,將參數(shù)應(yīng)用到所有服務(wù)器上并結(jié)束GC優(yōu)化

      如果GC優(yōu)化的結(jié)果令人滿意,就可以將參數(shù)應(yīng)用到所有服務(wù)器上,并停止GC優(yōu)化。

      在下面的章節(jié)中,你將會看到上述每一步所做的具體工作。

      監(jiān)控GC狀態(tài)并分析結(jié)果

      在運行中的Web應(yīng)用服務(wù)器(Web Application Server,WAS)上查看GC狀態(tài)的最佳方式就是使用?jstat命令。筆者在《如何監(jiān)控Java GC》中已經(jīng)介紹過了?jstat命令,所以在本篇文章中我將著重關(guān)注數(shù)據(jù)部分。

      下面的例子展示了某個還沒有執(zhí)行GC優(yōu)化的JVM的狀態(tài)(雖然它并不是運行服務(wù)器)。

      $?jstat?-gcutil?21719?1sS0????S1????E????O????P????YGC????YGCT????FGC????FGCT?GCT48.66?0.00?48.10?49.70?77.45?3428?172.623?3?59.050?231.67348.66?0.00?48.10?49.70?77.45?3428?172.623?3?59.050?231.673

      我們先看一下YGC(從應(yīng)用程序啟動到采樣時發(fā)生 Young GC 的次數(shù))和YGCT(從應(yīng)用程序啟動到采樣時 Young GC 所用的時間(秒)),計算YGCT/YGC會得出,平均每次新生代的GC耗時50ms,這是一個很小的數(shù)字,通過這個結(jié)果可以看出,我們大可不必關(guān)注新生代GC對GC性能的影響。

      現(xiàn)在來看一下FGC( 從應(yīng)用程序啟動到采樣時發(fā)生 Full GC 的次數(shù))和FGCT(從應(yīng)用程序啟動到采樣時 Full GC 所用的時間(秒)),計算FGCT/FGC會得出,平均每次老年代的GC耗時19.68s。有可能是執(zhí)行了三次Full GC,每次耗時19.68s,也有可能是有兩次只花了1s,另一次花了58s。不管是哪一種情況,GC優(yōu)化都是很有必要的。

      使用?jstat命令可以很容易地查看GC狀態(tài),但是分析GC的最佳方式是加上?-verbosegc參數(shù)來生成日志。在之前的文章中筆者已經(jīng)解釋了如何分析這些日志。HPJMeter是筆者最喜歡的用于分析?-verbosegc生成的日志的工具,它簡單易用,使用HPJmeter可以很容易地查看GC執(zhí)行時間以及GC發(fā)生頻率。

      此外,如果GC執(zhí)行時間滿足下列所有條件,就沒有必要進(jìn)行GC優(yōu)化了:

      Minor GC執(zhí)行非常迅速(50ms以內(nèi))

      Minor GC沒有頻繁執(zhí)行(大約10s執(zhí)行一次)

      Full GC執(zhí)行非常迅速(1s以內(nèi))

      Full GC沒有頻繁執(zhí)行(大約10min執(zhí)行一次)

      括號中的數(shù)字并不是絕對的,它們也隨著服務(wù)的狀態(tài)而變化。有些服務(wù)可能要求一次Full GC在0.9s以內(nèi),而有些則會放得更寬一些。因此,對于不同的服務(wù),需要按照不同的標(biāo)準(zhǔn)考慮是否需要執(zhí)行GC優(yōu)化。

      當(dāng)檢查GC狀態(tài)時,不能只查看Minor GC和Full GC的時間,還必須要關(guān)注GC執(zhí)行的次數(shù)。如果新生代空間太小,Minor GC將會非常頻繁地執(zhí)行(有時每秒會執(zhí)行一次,甚至更多)。此外,傳入老年代的對象數(shù)目會上升,從而導(dǎo)致Full GC的頻率升高。因此,在執(zhí)行?jstat命令時,請使用?-gccapacity參數(shù)來查看具體占用了多少空間。

      設(shè)置GC類型/內(nèi)存大小

      設(shè)置GC類型

      Oracle JVM有5種垃圾收集器,但是在JDK 7以前的版本中,你只能在Parallel GC, Parallel Compacting GC 和CMS GC之中選擇,至于具體選擇哪個,則沒有具體的原則和規(guī)則。

      既然這樣的話,我們?nèi)绾蝸磉x擇GC呢?最好的方法是把三種都用上,但是有一點必須明確——CMS GC通常比其他并行(Parallel)GC都要快(這是因為CMS GC是并發(fā)的GC),如果確實如此,那只選擇CMS GC就可以了,不過CMS GC也不總是更快,當(dāng)出現(xiàn)concurrent mode failure時,CMS GC就會比并行GC更慢了。

      Concurrent mode failure

      現(xiàn)在讓我們來深入地了解一下concurrent mode failure。

      并行GC和CMS GC的最大區(qū)別是并行GC采用“標(biāo)記-整理”(Mark-Compact)算法而CMS GC采用“標(biāo)記-清除”(Mark-Sweep)算法(具體內(nèi)容可參照譯者的文章《GC算法與內(nèi)存分配策略》),compact步驟就是通過移動內(nèi)存來消除內(nèi)存碎片,從而消除分配的內(nèi)存之間的空白區(qū)域。

      對于并行GC來說,無論何時執(zhí)行Full GC,都會進(jìn)行compact工作,這消耗了太多的時間。不過在執(zhí)行完Full GC后,下次內(nèi)存分配將會變得更快(因為直接順序分配相鄰的內(nèi)存)。

      相反,CMS GC沒有compact的過程,因此CMS GC運行的速度更快。但是也是由于沒有整理內(nèi)存,在進(jìn)行磁盤清理之前,內(nèi)存中會有很多零碎的空白區(qū)域,這也導(dǎo)致沒有足夠的空間分配給大對象。例如,在老年代還有300MB可用空間,但是連一個10MB的對象都沒有辦法被順序存儲在老年代中,在這種情況下,會報出“concurrent mode failure”的warning,然后系統(tǒng)執(zhí)行compact操作。但是CMS GC在這種情況下執(zhí)行的compact操作耗時要比并行GC高很多,并且這還會導(dǎo)致另一個問題,關(guān)于“concurrent mode failure”的詳細(xì)說明,可用參考Oracle工程師撰寫的《Understanding CMS GC Logs》。

      綜上所述,你需要根據(jù)你的系統(tǒng)情況為其選擇一個最適合的GC類型。

      每個系統(tǒng)都有最適合它的GC類型等著你去尋找,如果你有6臺服務(wù)器,我建議你每兩個服務(wù)器設(shè)置相同的參數(shù),然后加上?-verbosegc參數(shù)再分析結(jié)果。

      設(shè)置內(nèi)存大小

      下面展示了內(nèi)存大小、GC運行次數(shù)和GC運行時間之間的關(guān)系:

      jvm系列(九):如何優(yōu)化Java GC「譯」

      大內(nèi)存空間

      減少了GC的次數(shù)

      提高了GC的運行時間

      小內(nèi)存空間

      增多了GC的次數(shù)

      降低了GC的運行時間

      關(guān)于如何設(shè)置內(nèi)存的大小,沒有一個標(biāo)準(zhǔn)答案,如果服務(wù)器資源充足并且Full GC能在1s內(nèi)完成,把內(nèi)存設(shè)為10GB也是可以的,但是大部分服務(wù)器并不處在這種狀態(tài)中,當(dāng)內(nèi)存設(shè)為10GB時,F(xiàn)ull GC會耗時10-30s,具體的時間自然與對象的大小有關(guān)。

      既然如此,我們該如何設(shè)置內(nèi)存大小呢?通常我推薦設(shè)為500MB,這不是說你要通過?-Xms500m和?-Xmx500m參數(shù)來設(shè)置WAS內(nèi)存。根據(jù)GC優(yōu)化之前的狀態(tài),如果Full GC后還剩余300MB的空間,那么把內(nèi)存設(shè)為1GB是一個不錯的選擇(300MB(默認(rèn)程序占用)+ 500MB(老年代最小空間)+200MB(空閑內(nèi)存))。這意味著你需要為老年代設(shè)置至少500MB空間,因此如果你有三個運行服務(wù)器,可以把它們的內(nèi)存分別設(shè)置為1GB,1.5GB,2GB,然后檢查結(jié)果。

      理論上來說,GC執(zhí)行速度應(yīng)該遵循1GB> 1.5GB> 2GB,1GB內(nèi)存時GC執(zhí)行速度最快。然而,理論上的1GB內(nèi)存Full GC消耗1s、2GB內(nèi)存Full GC消耗2 s在現(xiàn)實里是無法保證的,實際的運行時間還依賴于服務(wù)器的性能和對象大小。因此,最好的方法是創(chuàng)建盡可能多的測量數(shù)據(jù)并監(jiān)控它們。

      在設(shè)置內(nèi)存空間大小時,你還需要設(shè)置一個參數(shù):?NewRatio。?NewRatio的值是新生代和老年代空間大小的比例。如果?XX:NewRatio=1,則新生代空間:老年代空間=1:1,如果堆內(nèi)存為1GB,則新生代:老年代=500MB:500MB。如果?NewRatio等于2,則新生代:老年代=1:2,因此,?NewRatio的值設(shè)置得越大,則老年代空間越大,新生代空間越小。

      你可能會認(rèn)為把?NewRatio設(shè)為1會是最好的選擇,然而事實并非如此,根據(jù)筆者的經(jīng)驗,當(dāng)?NewRatio設(shè)為2或3時,整個GC的狀態(tài)表現(xiàn)得更好。

      完成GC優(yōu)化最快地方法是什么?答案是比較性能測試的結(jié)果。為了給每臺服務(wù)器設(shè)置不同的參數(shù)并監(jiān)控它們,最好查看的是一或兩天后的數(shù)據(jù)。當(dāng)通過性能測試來進(jìn)行GC優(yōu)化時,你需要在不同的測試時保證它們有相同的負(fù)載和運行環(huán)境。然而,即使是專業(yè)的性能測試人員,想精確地控制負(fù)載也很困難,并且需要大量的時間準(zhǔn)備。因此,更加方便容易的方式是直接設(shè)置參數(shù)來運行,然后等待運行的結(jié)果(即使這需要消耗更多的時間)。

      分析GC優(yōu)化的結(jié)果

      在設(shè)置了GC參數(shù)和?-verbosegc參數(shù)后,可以使用tail命令確保日志被正確地生成。如果參數(shù)設(shè)置得不正確或日志未生成,那你的時間就被白白浪費了。如果日志收集沒有問題的話,在收集一或兩天數(shù)據(jù)后再檢查結(jié)果。最簡單的方法是把日志從服務(wù)器移到你的本地PC上,然后用HPJMeter分析數(shù)據(jù)。

      在分析結(jié)果時,請關(guān)注下列幾點(這個優(yōu)先級是筆者根據(jù)自己的經(jīng)驗擬定的,我認(rèn)為選取GC參數(shù)時應(yīng)考慮的最重要的因素是Full GC的運行時間。):

      單次Full GC運行時間

      單次Minor GC運行時間

      Full GC運行間隔

      Minor GC運行間隔

      整個Full GC的時間

      整個Minor GC的運行時間

      整個GC的運行時間

      Full GC的執(zhí)行次數(shù)

      Minor GC的執(zhí)行次數(shù)

      找到最佳的GC參數(shù)是件非常幸運的,然而在大多數(shù)時候,我們并不會如此幸運,在進(jìn)行GC優(yōu)化時一定要小心謹(jǐn)慎,因為當(dāng)你試圖一次完成所有的優(yōu)化工作時,可能會出現(xiàn)?OutOfMemoryError錯誤。

      優(yōu)化案例

      到目前為止,我們一直在從理論上介紹GC優(yōu)化,現(xiàn)在是時候?qū)⑦@些理論付諸實踐了,我們將通過幾個例子來更深入地理解GC優(yōu)化。

      示例1

      下面這個例子是針對Service S的優(yōu)化,對于最近剛開發(fā)出來的Service S,執(zhí)行Full GC需要消耗過多的時間。

      現(xiàn)在看一下執(zhí)行?jstat-gcutil的結(jié)果

      S0?S1?E?O?P?YGC?YGCT?FGC?FGCT?GCT12.16?0.00?5.18?63.78?20.32?54?2.047?5?6.946?8.993

      左邊的Perm區(qū)的值對于最初的GC優(yōu)化并不重要,而YGC參數(shù)的值更加對于這次優(yōu)化更為重要。

      平均執(zhí)行一次Minor GC和Full GC消耗的時間如下表所示:

      37ms對于Minor GC來說還不賴,但1.389s對于Full GC來說意味著當(dāng)GC發(fā)生在數(shù)據(jù)庫Timeout設(shè)置為1s的系統(tǒng)中時,可能會頻繁出現(xiàn)超時現(xiàn)象。

      首先,你需要檢查開始GC優(yōu)化前內(nèi)存的使用情況。使用?jstat-gccapacity命令可以檢查內(nèi)存用量情況。在筆者的服務(wù)器上查看到的結(jié)果如下:

      NGCMN?NGCMX?NGC?S0C?S1C?EC?OGCMN?OGCMX?OGC?OC?PGCMN?PGCMX?PGC?PC?YGC?FGC212992.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)鍵值如下:

      新生代內(nèi)存用量:212,992 KB

      老年代內(nèi)存用量:1,884,160 KB

      因此,除了永久代以外,被分配的內(nèi)存空間加起來有2GB,并且新生代:老年代=1:9,為了得到比使用?jstat更細(xì)致的結(jié)果,還需加上?-verbosegc參數(shù)獲取日志,并把三臺服務(wù)器按照如下方式設(shè)置(除此以外沒有使用任何其他參數(shù)):

      NewRatio=2

      NewRatio=3

      NewRatio=4

      一天后我得到了系統(tǒng)的GC log,幸運的是,在設(shè)置完NewRatio后系統(tǒng)沒有發(fā)生任何Full GC。

      這是為什么呢?這是因為大部分對象在創(chuàng)建后很快就被回收了,所有這些對象沒有被傳入老年代,而是在新生代就被銷毀回收了。

      在這樣的情況下,就沒有必要去改變其他的參數(shù)值了,只要選擇一個最合適的?NewRatio值即可。那么,如何確定最佳的NewRatio值呢?為此,我們分析一下每種?NewRatio值下Minor GC的平均響應(yīng)時間。

      在每種參數(shù)下Minor GC的平均響應(yīng)時間如下:

      NewRatio=2:45ms

      NewRatio=3:34ms

      NewRatio=4:30ms

      我們可以根據(jù)GC時間的長短得出NewRatio=4是最佳的參數(shù)值(盡管NewRatio=4時新生代空間是最小的)。在設(shè)置完GC參數(shù)后,服務(wù)器沒有發(fā)生Full GC。

      為了說明這個問題,下面是服務(wù)執(zhí)行一段時間后執(zhí)行?jstat–gcutil的結(jié)果:

      S0?S1?E?O?P?YGC?YGCT?FGC?FGCT?GCT8.61?0.00?30.67?24.62?22.38?2424?30.219?0?0.000?30.219

      你可能會認(rèn)為是服務(wù)器接收的請求少才使得GC發(fā)生的頻率較低,實際上,雖然Full GC沒有執(zhí)行過,但Minor GC被執(zhí)行了2424次。

      示例2

      這是一個Service A的例子。我們通過公司內(nèi)部的應(yīng)用性能管理系統(tǒng)(APM)發(fā)現(xiàn)JVM暫停了相當(dāng)長的時間(超過8秒),因此我們進(jìn)行了GC優(yōu)化。我們努力尋找JVM暫停的原因,后來發(fā)現(xiàn)是因為Full GC執(zhí)行時間過長,因此我們決定進(jìn)行GC優(yōu)化。

      在GC優(yōu)化的開始階段,我們加上了?-verbosegc參數(shù),結(jié)果如下圖所示:

      上圖是由HPJMeter生成的圖片之一。橫坐標(biāo)表示JVM執(zhí)行的時間,縱坐標(biāo)表示每次GC的時間。CMS為綠點,表示Full GC的結(jié)果,而Parallel Scavenge為藍(lán)點,表示Minor GC的結(jié)果。

      之前我說過CMS GC是最快的GC,但是上面的結(jié)果顯示在一些時候CMS耗時達(dá)到了15s。是什么導(dǎo)致了這一結(jié)果?請記住我之前說的:CMS在執(zhí)行compact(整理)操作時會顯著變慢。此外,服務(wù)的內(nèi)存通過?-Xms1g和?=Xmx4g設(shè)置了,而分配的內(nèi)存只有4GB。

      因此筆者將GC類型從CMS GC改為了Parallel GC,把內(nèi)存大小設(shè)為2GB,并把?NewRatio設(shè)為3。在執(zhí)行?jstat-gcutil幾小時后的結(jié)果如下:

      S0?S1?E?O?P?YGC?YGCT?FGC?FGCT?GCT0.00?30.48?3.31?26.54?37.01?226?11.131?4?11.758?22.890

      Full GC的時間縮短了,變成了每次3s,跟15s比有了顯著提升。但是3s依然不夠快,為此筆者創(chuàng)建了以下6種情況:

      Case 1:?-XX:+UseParallelGC-Xms1536m-Xmx1536m-XX:NewRatio=2

      Case 2:?-XX:+UseParallelGC-Xms1536m-Xmx1536m-XX:NewRatio=3

      Case 3:?-XX:+UseParallelGC-Xms1g-Xmx1g-XX:NewRatio=3

      Case 4:?-XX:+UseParallelOldGC-Xms1536m-Xmx1536m-XX:NewRatio=2

      Case 5:?-XX:+UseParallelOldGC-Xms1536m-Xmx1536m-XX:NewRatio=3

      Case 6:?-XX:+UseParallelOldGC-Xms1g-Xmx1g-XX:NewRatio=3

      上面哪一種情況最快?結(jié)果顯示,內(nèi)存空間越小,運行結(jié)果最少。下圖展示了性能最好的Case 6的結(jié)果圖,它的最慢響應(yīng)時間只有1.7s,并且響應(yīng)時間的平均值已經(jīng)被 控制到了1s以內(nèi)。

      基于上圖的結(jié)果,按照Case 6調(diào)整了GC參數(shù),但這卻導(dǎo)致每晚都會發(fā)生?OutOfMemoryError。很難解釋發(fā)生異常的具體原因,簡單地說,應(yīng)該是批處理程序?qū)е铝藘?nèi)存泄漏,我們正在解決相關(guān)的問題。

      如果只對GC日志做一些短時間的分析就將相關(guān)參數(shù)部署到所有服務(wù)器上來執(zhí)行GC優(yōu)化,這將是非常危險的。切記,只有當(dāng)你同時仔細(xì)分析服務(wù)的執(zhí)行情況和GC日志后,才能保證GC優(yōu)化沒有錯誤地執(zhí)行。

      在上文中,我們通過兩個GC優(yōu)化的例子來說明了GC優(yōu)化是怎樣執(zhí)行的。正如上文中提到的,例子中設(shè)置的GC參數(shù)可以設(shè)置在相同的服務(wù)器之上,但前提是他們具有相同的CPU、操作系統(tǒng)、JDK版本并且運行著相同的服務(wù)。此外,不要把我使用的參數(shù)照搬到你的應(yīng)用上,它們可能在你的機(jī)器上并不能起到同樣良好的效果。

      總結(jié)

      筆者沒有執(zhí)行heap dump并分析內(nèi)存的詳細(xì)內(nèi)容,而是通過自己的經(jīng)驗進(jìn)行GC優(yōu)化。精確地分析內(nèi)存可以得到更好的優(yōu)化效果,不過這種分析一般只適用于內(nèi)存使用量相對固定的場景。如果服務(wù)嚴(yán)重過載并占有了大量的內(nèi)存,則建議你根據(jù)之前的經(jīng)驗進(jìn)行GC優(yōu)化。

      筆者已經(jīng)在一些服務(wù)上設(shè)置了G1 GC參數(shù)并進(jìn)行了性能測試,但還沒有應(yīng)用于正式的生產(chǎn)環(huán)境。G1 GC的速度快于任何其他的GC類型,但是你必須要升級到JDK 7。此外,暫時還無法保證它的穩(wěn)定性,沒有人知道運行時是否會出現(xiàn)致命的錯誤,因此G1 GC暫時還不適合投入應(yīng)用。

      等未來JDK 7真正穩(wěn)定了(這并不是說它現(xiàn)在不穩(wěn)定),并且WAS針對JDK 7進(jìn)行優(yōu)化后,G1 GC最終能按照預(yù)期的那樣來工作,等到那一天我們可能就不再需要GC優(yōu)化了。

      Java JVM

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:WPS表格里乘法怎么做
      下一篇:使用ERP進(jìn)行項目作業(yè)成本核算
      相關(guān)文章
      亚洲无人区午夜福利码高清完整版 | 久久精品国产亚洲av麻豆色欲| 亚洲综合另类小说色区色噜噜| 亚洲中文字幕无码中文字| 亚洲一区二区影院| 亚洲成人激情在线| 久久亚洲国产午夜精品理论片 | 亚洲经典千人经典日产| 亚洲不卡影院午夜在线观看| 亚洲AV无码一区二区三区在线| 亚洲码一区二区三区| 久久国产亚洲精品无码| 91在线亚洲精品专区| 精品亚洲A∨无码一区二区三区| 日木av无码专区亚洲av毛片| 亚洲综合精品一二三区在线| 亚洲狠狠久久综合一区77777| 久久久久久亚洲精品| 亚洲综合一区二区精品导航| 久久久亚洲欧洲日产国码二区| 亚洲视频小说图片| 亚洲欧洲久久精品| 亚洲一级毛片免费看| 亚洲高清一区二区三区| 亚洲一日韩欧美中文字幕在线| 亚洲码和欧洲码一码二码三码| 亚洲AV无码专区在线观看成人| 亚洲爆乳无码精品AAA片蜜桃| 在线观看国产一区亚洲bd| 人人狠狠综合久久亚洲高清| 亚洲男人av香蕉爽爽爽爽| 国产亚洲精品影视在线产品| 亚洲第一精品电影网| 亚洲另类古典武侠| 亚洲人成电影网站色| 国产亚洲精品美女久久久久久下载| 全亚洲最新黄色特级网站 | 国产亚洲精品无码拍拍拍色欲| 亚洲精品无码成人片久久| 亚洲精品无码午夜福利中文字幕| 国产成人亚洲精品青草天美|