从实际案例聊聊Java应用的GC优化--转
https://tech.meituan.com/jvm_optimize.html
當(dāng)Java程序性能達(dá)不到既定目標(biāo),且其他優(yōu)化手段都已經(jīng)窮盡時(shí),通常需要調(diào)整垃圾回收器來進(jìn)一步提高性能,稱為GC優(yōu)化。但GC算法復(fù)雜,影響GC性能的參數(shù)眾多,且參數(shù)調(diào)整又依賴于應(yīng)用各自的特點(diǎn),這些因素很大程度上增加了GC優(yōu)化的難度。即便如此,GC調(diào)優(yōu)也不是無章可循,仍然有一些通用的思考方法。本篇會(huì)介紹這些通用的GC優(yōu)化策略和相關(guān)實(shí)踐案例,主要包括如下內(nèi)容:
優(yōu)化前準(zhǔn)備: 簡(jiǎn)單回顧JVM相關(guān)知識(shí)、介紹GC優(yōu)化的一些通用策略。
優(yōu)化方法: 介紹調(diào)優(yōu)的一般流程:明確優(yōu)化目標(biāo)→優(yōu)化→跟蹤優(yōu)化結(jié)果。
優(yōu)化案例: 簡(jiǎn)述筆者所在團(tuán)隊(duì)遇到的GC問題以及優(yōu)化方案。
一、優(yōu)化前的準(zhǔn)備
GC優(yōu)化需知
為了更好地理解本篇所介紹的內(nèi)容,你需要了解如下內(nèi)容。
GC相關(guān)基礎(chǔ)知識(shí),包括但不限于:
a) GC工作原理。
b) 理解新生代、老年代、晉升等術(shù)語含義。
c) 可以看懂GC日志。
GC優(yōu)化不能解決一切性能問題,它是最后的調(diào)優(yōu)手段。
如果對(duì)第一點(diǎn)中提及的知識(shí)點(diǎn)不是很熟悉,可以先閱讀小結(jié)-JVM基礎(chǔ)回顧;如果已經(jīng)很熟悉,可以跳過該節(jié)直接往下閱讀。
JVM基礎(chǔ)回顧
JVM內(nèi)存結(jié)構(gòu)
簡(jiǎn)單介紹一下JVM內(nèi)存結(jié)構(gòu)和常見的垃圾回收器。
當(dāng)代主流虛擬機(jī)(Hotspot VM)的垃圾回收都采用“分代回收”的算法。“分代回收”是基于這樣一個(gè)事實(shí):對(duì)象的生命周期不同,所以針對(duì)不同生命周期的對(duì)象可以采取不同的回收方式,以便提高回收效率。
Hotspot VM將內(nèi)存劃分為不同的物理區(qū),就是“分代”思想的體現(xiàn)。如圖所示,JVM內(nèi)存主要由新生代、老年代、永久代構(gòu)成。
① 新生代(Young Generation):大多數(shù)對(duì)象在新生代中被創(chuàng)建,其中很多對(duì)象的生命周期很短。每次新生代的垃圾回收(又稱Minor GC)后只有少量對(duì)象存活,所以選用復(fù)制算法,只需要少量的復(fù)制成本就可以完成回收。
新生代內(nèi)又分三個(gè)區(qū):一個(gè)Eden區(qū),兩個(gè)Survivor區(qū)(一般而言),大部分對(duì)象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí),還存活的對(duì)象將被復(fù)制到兩個(gè)Survivor區(qū)(中的一個(gè))。當(dāng)這個(gè)Survivor區(qū)滿時(shí),此區(qū)的存活且不滿足“晉升”條件的對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū)。對(duì)象每經(jīng)歷一次Minor GC,年齡加1,達(dá)到“晉升年齡閾值”后,被放到老年代,這個(gè)過程也稱為“晉升”。顯然,“晉升年齡閾值”的大小直接影響著對(duì)象在新生代中的停留時(shí)間,在Serial和ParNew GC兩種回收器中,“晉升年齡閾值”通過參數(shù)MaxTenuringThreshold設(shè)定,默認(rèn)值為15。
② 老年代(Old Generation):在新生代中經(jīng)歷了N次垃圾回收后仍然存活的對(duì)象,就會(huì)被放到年老代,該區(qū)域中對(duì)象存活率高。老年代的垃圾回收(又稱Major GC)通常使用“標(biāo)記-清理”或“標(biāo)記-整理”算法。整堆包括新生代和老年代的垃圾回收稱為Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都會(huì)同時(shí)收集整個(gè)GC堆,包括新生代)。
③ 永久代(Perm Generation):主要存放元數(shù)據(jù),例如Class、Method的元信息,與垃圾回收要回收的Java對(duì)象關(guān)系不大。相對(duì)于新生代和年老代來說,該區(qū)域的劃分對(duì)垃圾回收影響比較小。
常見垃圾回收器
不同的垃圾回收器,適用于不同的場(chǎng)景。常用的垃圾回收器:
- 串行(Serial)回收器是單線程的一個(gè)回收器,簡(jiǎn)單、易實(shí)現(xiàn)、效率高。
- 并行(ParNew)回收器是Serial的多線程版,可以充分的利用CPU資源,減少回收的時(shí)間。
- 吞吐量?jī)?yōu)先(Parallel Scavenge)回收器,側(cè)重于吞吐量的控制。
- 并發(fā)標(biāo)記清除(CMS,Concurrent Mark Sweep)回收器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的回收器,該回收器是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的。
GC日志
每一種回收器的日志格式都是由其自身的實(shí)現(xiàn)決定的,換而言之,每種回收器的日志格式都可以不一樣。但虛擬機(jī)設(shè)計(jì)者為了方便用戶閱讀,將各個(gè)回收器的日志都維持一定的共性。JavaGC日志?中簡(jiǎn)單介紹了這些共性。
參數(shù)基本策略
各分區(qū)的大小對(duì)GC的性能影響很大。如何將各分區(qū)調(diào)整到合適的大小,分析活躍數(shù)據(jù)的大小是很好的切入點(diǎn)。
活躍數(shù)據(jù)的大小是指,應(yīng)用程序穩(wěn)定運(yùn)行時(shí)長(zhǎng)期存活對(duì)象在堆中占用的空間大小,也就是Full GC后堆中老年代占用空間的大小。可以通過GC日志中Full GC之后老年代數(shù)據(jù)大小得出,比較準(zhǔn)確的方法是在程序穩(wěn)定后,多次獲取GC數(shù)據(jù),通過取平均值的方式計(jì)算活躍數(shù)據(jù)的大小。活躍數(shù)據(jù)和各分區(qū)之間的比例關(guān)系如下(見參考文獻(xiàn)1):
| 總大小 | 3-4?倍活躍數(shù)據(jù)的大小 |
| 新生代 | 1-1.5?活躍數(shù)據(jù)的大小 |
| 老年代 | 2-3?倍活躍數(shù)據(jù)的大小 |
| 永久代 | 1.2-1.5?倍Full GC后的永久代空間占用 |
例如,根據(jù)GC日志獲得老年代的活躍數(shù)據(jù)大小為300M,那么各分區(qū)大小可以設(shè)為:
總堆:1200MB = 300MB × 4
新生代:450MB = 300MB × 1.5
老年代: 750MB = 1200MB - 450MB*
這部分設(shè)置僅僅是堆大小的初始值,后面的優(yōu)化中,可能會(huì)調(diào)整這些值,具體情況取決于應(yīng)用程序的特性和需求。
二、優(yōu)化步驟
GC優(yōu)化一般步驟可以概括為:確定目標(biāo)、優(yōu)化參數(shù)、驗(yàn)收結(jié)果。
確定目標(biāo)
明確應(yīng)用程序的系統(tǒng)需求是性能優(yōu)化的基礎(chǔ),系統(tǒng)的需求是指應(yīng)用程序運(yùn)行時(shí)某方面的要求,譬如:
- 高可用,可用性達(dá)到幾個(gè)9。
- 低延遲,請(qǐng)求必須多少毫秒內(nèi)完成響應(yīng)。
- 高吞吐,每秒完成多少次事務(wù)。
明確系統(tǒng)需求之所以重要,是因?yàn)樯鲜鲂阅苤笜?biāo)間可能沖突。比如通常情況下,縮小延遲的代價(jià)是降低吞吐量或者消耗更多的內(nèi)存或者兩者同時(shí)發(fā)生。
由于筆者所在團(tuán)隊(duì)主要關(guān)注高可用和低延遲兩項(xiàng)指標(biāo),所以接下來分析,如何量化GC時(shí)間和頻率對(duì)于響應(yīng)時(shí)間和可用性的影響。通過這個(gè)量化指標(biāo),可以計(jì)算出當(dāng)前GC情況對(duì)服務(wù)的影響,也能評(píng)估出GC優(yōu)化后對(duì)響應(yīng)時(shí)間的收益,這兩點(diǎn)對(duì)于低延遲服務(wù)很重要。
舉例:假設(shè)單位時(shí)間T內(nèi)發(fā)生一次持續(xù)25ms的GC,接口平均響應(yīng)時(shí)間為50ms,且請(qǐng)求均勻到達(dá),根據(jù)下圖所示:
那么有(50ms+25ms)/T比例的請(qǐng)求會(huì)受GC影響,其中GC前的50ms內(nèi)到達(dá)的請(qǐng)求都會(huì)增加25ms,GC期間的25ms內(nèi)到達(dá)的請(qǐng)求,會(huì)增加0-25ms不等,如果時(shí)間T內(nèi)發(fā)生N次GC,受GC影響請(qǐng)求占比=(接口響應(yīng)時(shí)間+GC時(shí)間)×N/T?。可見無論降低單次GC時(shí)間還是降低GC次數(shù)N都可以有效減少GC對(duì)響應(yīng)時(shí)間的影響。
優(yōu)化
通過收集GC信息,結(jié)合系統(tǒng)需求,確定優(yōu)化方案,例如選用合適的GC回收器、重新設(shè)置內(nèi)存比例、調(diào)整JVM參數(shù)等。
進(jìn)行調(diào)整后,將不同的優(yōu)化方案分別應(yīng)用到多臺(tái)機(jī)器上,然后比較這些機(jī)器上GC的性能差異,有針對(duì)性的做出選擇,再通過不斷的試驗(yàn)和觀察,找到最合適的參數(shù)。
驗(yàn)收優(yōu)化結(jié)果
將修改應(yīng)用到所有服務(wù)器,判斷優(yōu)化結(jié)果是否符合預(yù)期,總結(jié)相關(guān)經(jīng)驗(yàn)。
接下來,我們通過三個(gè)案例來實(shí)踐以上的優(yōu)化流程和基本原則(本文中三個(gè)案例使用的垃圾回收器均為ParNew+CMS,CMS失敗時(shí)Serial Old替補(bǔ))。
三、GC優(yōu)化案例
案例一 Major GC和Minor GC頻繁
確定目標(biāo)
服務(wù)情況:Minor GC每分鐘100次 ,Major GC每4分鐘一次,單次Minor GC耗時(shí)25ms,單次Major GC耗時(shí)200ms,接口響應(yīng)時(shí)間50ms。
由于這個(gè)服務(wù)要求低延時(shí)高可用,結(jié)合上文中提到的GC對(duì)服務(wù)響應(yīng)時(shí)間的影響,計(jì)算可知由于Minor GC的發(fā)生,12.5%的請(qǐng)求響應(yīng)時(shí)間會(huì)增加,其中8.3%的請(qǐng)求響應(yīng)時(shí)間會(huì)增加25ms,可見當(dāng)前GC情況對(duì)響應(yīng)時(shí)間影響較大。
(50ms+25ms)× 100次/60000ms = 12.5%,50ms × 100次/60000ms = 8.3%?。
優(yōu)化目標(biāo):降低TP99、TP90時(shí)間。
優(yōu)化
首先優(yōu)化Minor GC頻繁問題。通常情況下,由于新生代空間較小,Eden區(qū)很快被填滿,就會(huì)導(dǎo)致頻繁Minor GC,因此可以通過增大新生代空間來降低Minor GC的頻率。例如在相同的內(nèi)存分配率的前提下,新生代中的Eden區(qū)增加一倍,Minor GC的次數(shù)就會(huì)減少一半。
這時(shí)很多人有這樣的疑問,擴(kuò)容Eden區(qū)雖然可以減少M(fèi)inor GC的次數(shù),但會(huì)增加單次Minor GC時(shí)間么?根據(jù)上面公式,如果單次Minor GC時(shí)間也增加,很難保證最后的優(yōu)化效果。我們結(jié)合下面情況來分析,單次Minor GC時(shí)間主要受哪些因素影響?是否和新生代大小存在線性關(guān)系?
首先,單次Minor GC時(shí)間由以下兩部分組成:T1(掃描新生代)和 T2(復(fù)制存活對(duì)象到Survivor區(qū))如下圖。(注:這里為了簡(jiǎn)化問題,我們認(rèn)為T1只掃描新生代判斷對(duì)象是否存活的時(shí)間,其實(shí)該階段還需要掃描部分老年代,后面案例中有詳細(xì)描述。)
-
擴(kuò)容前:新生代容量為R ,假設(shè)對(duì)象A的存活時(shí)間為750ms,Minor GC間隔500ms,那么本次Minor GC時(shí)間= T1(掃描新生代R)+T2(復(fù)制對(duì)象A到S)。
-
擴(kuò)容后:新生代容量為2R ,對(duì)象A的生命周期為750ms,那么Minor GC間隔增加為1000ms,此時(shí)Minor GC對(duì)象A已不再存活,不需要把它復(fù)制到Survivor區(qū),那么本次GC時(shí)間 = 2 × T1(掃描新生代R),沒有T2復(fù)制時(shí)間。
可見,擴(kuò)容后,Minor GC時(shí)增加了T1(掃描時(shí)間),但省去T2(復(fù)制對(duì)象)的時(shí)間,更重要的是對(duì)于虛擬機(jī)來說,復(fù)制對(duì)象的成本要遠(yuǎn)高于掃描成本,所以,單次Minor GC時(shí)間更多取決于GC后存活對(duì)象的數(shù)量,而非Eden區(qū)的大小。因此如果堆中短期對(duì)象很多,那么擴(kuò)容新生代,單次Minor GC時(shí)間不會(huì)顯著增加。下面需要確認(rèn)下服務(wù)中對(duì)象的生命周期分布情況:
通過上圖GC日志中兩處紅色框標(biāo)記內(nèi)容可知:
由此可見,服務(wù)中存在大量短期臨時(shí)對(duì)象,擴(kuò)容新生代空間后,Minor GC頻率降低,對(duì)象在新生代得到充分回收,只有生命周期長(zhǎng)的對(duì)象才進(jìn)入老年代。這樣老年代增速變慢,Major GC頻率自然也會(huì)降低。
優(yōu)化結(jié)果
通過擴(kuò)容新生代為為原來的三倍,單次Minor GC時(shí)間增加小于5ms,頻率下降了60%,服務(wù)響應(yīng)時(shí)間TP90,TP99都下降了10ms+,服務(wù)可用性得到提升。
調(diào)整前:
調(diào)整后:
小結(jié)
如何選擇各分區(qū)大小應(yīng)該依賴應(yīng)用程序中對(duì)象生命周期的分布情況:如果應(yīng)用存在大量的短期對(duì)象,應(yīng)該選擇較大的年輕代;如果存在相對(duì)較多的持久對(duì)象,老年代應(yīng)該適當(dāng)增大。
更多思考
關(guān)于上文中提到晉升年齡閾值為2,很多同學(xué)有疑問,為什么設(shè)置了MaxTenuringThreshold=15,對(duì)象仍然僅經(jīng)歷2次Minor GC,就晉升到老年代?這里涉及到“動(dòng)態(tài)年齡計(jì)算”的概念。
動(dòng)態(tài)年齡計(jì)算:Hotspot遍歷所有對(duì)象時(shí),按照年齡從小到大對(duì)其所占用的大小進(jìn)行累積,當(dāng)累積的某個(gè)年齡大小超過了survivor區(qū)的一半時(shí),取這個(gè)年齡和MaxTenuringThreshold中更小的一個(gè)值,作為新的晉升年齡閾值。在本案例中,調(diào)優(yōu)前:Survivor區(qū) = 64M,desired survivor = 32M,此時(shí)Survivor區(qū)中age<=2的對(duì)象累計(jì)大小為41M,41M大于32M,所以晉升年齡閾值被設(shè)置為2,下次Minor GC時(shí)將年齡超過2的對(duì)象被晉升到老年代。
JVM引入動(dòng)態(tài)年齡計(jì)算,主要基于如下兩點(diǎn)考慮:
如果固定按照MaxTenuringThreshold設(shè)定的閾值作為晉升條件:
a)MaxTenuringThreshold設(shè)置的過大,原本應(yīng)該晉升的對(duì)象一直停留在Survivor區(qū),直到Survivor區(qū)溢出,一旦溢出發(fā)生,Eden+Svuvivor中對(duì)象將不再依據(jù)年齡全部提升到老年代,這樣對(duì)象老化的機(jī)制就失效了。
b)MaxTenuringThreshold設(shè)置的過小,“過早晉升”即對(duì)象不能在新生代充分被回收,大量短期對(duì)象被晉升到老年代,老年代空間迅速增長(zhǎng),引起頻繁的Major GC。分代回收失去了意義,嚴(yán)重影響GC性能。
相同應(yīng)用在不同時(shí)間的表現(xiàn)不同:特殊任務(wù)的執(zhí)行或者流量成分的變化,都會(huì)導(dǎo)致對(duì)象的生命周期分布發(fā)生波動(dòng),那么固定的閾值設(shè)定,因?yàn)闊o法動(dòng)態(tài)適應(yīng)變化,會(huì)造成和上面相同的問題。
總結(jié)來說,為了更好的適應(yīng)不同程序的內(nèi)存情況,虛擬機(jī)并不總是要求對(duì)象年齡必須達(dá)到Maxtenuringthreshhold再晉級(jí)老年代。
案例二 請(qǐng)求高峰期發(fā)生GC,導(dǎo)致服務(wù)可用性下降
確定目標(biāo)
GC日志顯示,高峰期CMS在重標(biāo)記(Remark)階段耗時(shí)1.39s。Remark階段是Stop-The-World(以下簡(jiǎn)稱為STW)的,即在執(zhí)行垃圾回收時(shí),Java應(yīng)用程序中除了垃圾回收器線程之外其他所有線程都被掛起,意味著在此期間,用戶正常工作的線程全部被暫停下來,這是低延時(shí)服務(wù)不能接受的。本次優(yōu)化目標(biāo)是降低Remark時(shí)間。
優(yōu)化
解決問題前,先回顧一下CMS的四個(gè)主要階段,以及各個(gè)階段的工作內(nèi)容。下圖展示了CMS各個(gè)階段可以標(biāo)記的對(duì)象,用不同顏色區(qū)分。
可見,Remark階段主要是通過掃描堆來判斷對(duì)象是否存活。那么準(zhǔn)確判斷對(duì)象是否存活,需要掃描哪些對(duì)象?CMS對(duì)老年代做回收,Remark階段僅掃描老年代是否可行?結(jié)論是不可行,原因如下:
如果僅掃描老年代中對(duì)象,即以老年代中對(duì)象為根,判斷對(duì)象是否存在引用,上圖中,對(duì)象A因?yàn)橐么嬖谛律?#xff0c;它在Remark階段就不會(huì)被修正標(biāo)記為可達(dá),GC時(shí)會(huì)被錯(cuò)誤回收。
新生代對(duì)象持有老年代中對(duì)象的引用,這種情況稱為“跨代引用”。因它的存在,Remark階段必須掃描整個(gè)堆來判斷對(duì)象是否存活,包括圖中灰色的不可達(dá)對(duì)象。
灰色對(duì)象已經(jīng)不可達(dá),但仍然需要掃描的原因:新生代GC和老年代的GC是各自分開獨(dú)立進(jìn)行的,只有Minor GC時(shí)才會(huì)使用根搜索算法,標(biāo)記新生代對(duì)象是否可達(dá),也就是說雖然一些對(duì)象已經(jīng)不可達(dá),但在Minor GC發(fā)生前不會(huì)被標(biāo)記為不可達(dá),CMS也無法辨認(rèn)哪些對(duì)象存活,只能全堆掃描(新生代+老年代)。由此可見堆中對(duì)象的數(shù)目影響了Remark階段耗時(shí)。
分析GC日志可以得出同樣的規(guī)律,Remark耗時(shí)>500ms時(shí),新生代使用率都在75%以上。這樣降低Remark階段耗時(shí)問題轉(zhuǎn)換成如何減少新生代對(duì)象數(shù)量。
新生代中對(duì)象的特點(diǎn)是“朝生夕滅”,這樣如果Remark前執(zhí)行一次Minor GC,大部分對(duì)象就會(huì)被回收。CMS就采用了這樣的方式,在Remark前增加了一個(gè)可中斷的并發(fā)預(yù)清理(CMS-concurrent-abortable-preclean),該階段主要工作仍然是并發(fā)標(biāo)記對(duì)象是否存活,只是這個(gè)過程可被中斷。此階段在Eden區(qū)使用超過2M時(shí)啟動(dòng),當(dāng)然2M是默認(rèn)的閾值,可以通過參數(shù)修改。如果此階段執(zhí)行時(shí)等到了Minor GC,那么上述灰色對(duì)象將被回收,Reamark階段需要掃描的對(duì)象就少了。
除此之外CMS為了避免這個(gè)階段沒有等到Minor GC而陷入無限等待,提供了參數(shù)CMSMaxAbortablePrecleanTime ,默認(rèn)為5s,含義是如果可中斷的預(yù)清理執(zhí)行超過5s,不管發(fā)沒發(fā)生Minor GC,都會(huì)中止此階段,進(jìn)入Remark。
根據(jù)GC日志紅色標(biāo)記2處顯示,可中斷的并發(fā)預(yù)清理執(zhí)行了5.35s,超過了設(shè)置的5s被中斷,期間沒有等到Minor GC ,所以Remark時(shí)新生代中仍然有很多對(duì)象。
對(duì)于這種情況,CMS提供CMSScavengeBeforeRemark參數(shù),用來保證Remark前強(qiáng)制進(jìn)行一次Minor GC。
優(yōu)化結(jié)果
經(jīng)過增加CMSScavengeBeforeRemark參數(shù),單次執(zhí)行時(shí)間>200ms的GC停頓消失,從監(jiān)控上觀察,GCtime和業(yè)務(wù)波動(dòng)保持一致,不再有明顯的毛刺。
小結(jié)
通過案例分析了解到,由于跨代引用的存在,CMS在Remark階段必須掃描整個(gè)堆,同時(shí)為了避免掃描時(shí)新生代有很多對(duì)象,增加了可中斷的預(yù)清理階段用來等待Minor GC的發(fā)生。只是該階段有時(shí)間限制,如果超時(shí)等不到Minor GC,Remark時(shí)新生代仍然有很多對(duì)象,我們的調(diào)優(yōu)策略是,通過參數(shù)強(qiáng)制Remark前進(jìn)行一次Minor GC,從而降低Remark階段的時(shí)間。
更多思考
案例中只涉及老年代GC,其實(shí)新生代GC存在同樣的問題,即老年代可能持有新生代對(duì)象引用,所以Minor GC時(shí)也必須掃描老年代。
JVM是如何避免Minor GC時(shí)掃描全堆的?
經(jīng)過統(tǒng)計(jì)信息顯示,老年代持有新生代對(duì)象引用的情況不足1%,根據(jù)這一特性JVM引入了卡表(card table)來實(shí)現(xiàn)這一目的。如下圖所示:
卡表的具體策略是將老年代的空間分成大小為512B的若干張卡(card)。卡表本身是單字節(jié)數(shù)組,數(shù)組中的每個(gè)元素對(duì)應(yīng)著一張卡,當(dāng)發(fā)生老年代引用新生代時(shí),虛擬機(jī)將該卡對(duì)應(yīng)的卡表元素設(shè)置為適當(dāng)?shù)闹怠H缟蠄D所示,卡表3被標(biāo)記為臟(卡表還有另外的作用,標(biāo)識(shí)并發(fā)標(biāo)記階段哪些塊被修改過),之后Minor GC時(shí)通過掃描卡表就可以很快的識(shí)別哪些卡中存在老年代指向新生代的引用。這樣虛擬機(jī)通過空間換時(shí)間的方式,避免了全堆掃描。
總結(jié)來說,CMS的設(shè)計(jì)聚焦在獲取最短的時(shí)延,為此它“不遺余力”地做了很多工作,包括盡量讓應(yīng)用程序和GC線程并發(fā)、增加可中斷的并發(fā)預(yù)清理階段、引入卡表等,雖然這些操作犧牲了一定吞吐量但獲得了更短的回收停頓時(shí)間。
案例三 發(fā)生Stop-The-World的GC
確定目標(biāo)
GC日志如下圖(在GC日志中,Full GC是用來說明這次垃圾回收的停頓類型,代表STW類型的GC,并不特指老年代GC),根據(jù)GC日志可知本次Full GC耗時(shí)1.23s。這個(gè)在線服務(wù)同樣要求低時(shí)延高可用。本次優(yōu)化目標(biāo)是降低單次STW回收停頓時(shí)間,提高可用性。
優(yōu)化
首先,什么時(shí)候可能會(huì)觸發(fā)STW的Full GC呢?
然后,我們來逐一分析一下:
- 排除原因2:如果是原因2中兩種情況,日志中會(huì)有特殊標(biāo)識(shí),目前沒有。
- 排除原因3:根據(jù)GC日志,當(dāng)時(shí)老年代使用量?jī)H為20%,也不存在大于2G的大對(duì)象產(chǎn)生。
- 排除原因4:因?yàn)楫?dāng)時(shí)沒有相關(guān)命令執(zhí)行。
- 鎖定原因1:根據(jù)日志發(fā)現(xiàn)Full GC后,Perm區(qū)變大了,推斷是由于永久代空間不足容量擴(kuò)展導(dǎo)致的。
找到原因后解決方法有兩種:
由于該服務(wù)沒有生成大量動(dòng)態(tài)類,回收Perm區(qū)收益不大,所以我們采用方案1,啟動(dòng)時(shí)將Perm區(qū)大小固定,避免進(jìn)行動(dòng)態(tài)擴(kuò)容。
優(yōu)化結(jié)果
調(diào)整參數(shù)后,服務(wù)不再有Perm區(qū)擴(kuò)容導(dǎo)致的STW GC發(fā)生。
小結(jié)
對(duì)于性能要求很高的服務(wù),建議將MaxPermSize和MinPermSize設(shè)置成一致(JDK8開始,Perm區(qū)完全消失,轉(zhuǎn)而使用元空間。而元空間是直接存在內(nèi)存中,不在JVM中),Xms和Xmx也設(shè)置為相同,這樣可以減少內(nèi)存自動(dòng)擴(kuò)容和收縮帶來的性能損失。虛擬機(jī)啟動(dòng)的時(shí)候就會(huì)把參數(shù)中所設(shè)定的內(nèi)存全部化為私有,即使擴(kuò)容前有一部分內(nèi)存不會(huì)被用戶代碼用到,這部分內(nèi)存在虛擬機(jī)中被標(biāo)識(shí)為虛擬內(nèi)存,也不會(huì)交給其他進(jìn)程使用。
四、總結(jié)
結(jié)合上述GC優(yōu)化案例做個(gè)總結(jié):
本文中案例均來北京業(yè)務(wù)安全中心(也稱風(fēng)控)對(duì)接服務(wù)的實(shí)踐經(jīng)驗(yàn)。同時(shí)感謝風(fēng)控的小伙伴們,是他們專業(yè)負(fù)責(zé)的審閱,才讓這篇文章更加完善。對(duì)于本文中涉及到的內(nèi)容,歡迎大家指正和補(bǔ)充。
參考文獻(xiàn)
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/8745403.html
總結(jié)
以上是生活随笔為你收集整理的从实际案例聊聊Java应用的GC优化--转的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 问问大家作为大数据总监,刚刚到一家新单位
- 下一篇: MySQL 四种事务隔离级别详解及对比-