中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

JVM:從實際案例聊(liao)聊(liao)Java應用的(de)GC優化

原文(wen)轉載自美團,感謝原作者(zhe)的貢(gong)獻


 

當Java程序性能(neng)達(da)不(bu)到既(ji)定目標,且其他(ta)優(you)(you)化手段都已經窮盡時,通常需要(yao)調(diao)(diao)整垃圾(ji)回收器來進(jin)一步(bu)提高性能(neng),稱(cheng)為(wei)GC優(you)(you)化。但GC算法(fa)復雜,影響GC性能(neng)的(de)參數眾多(duo),且參數調(diao)(diao)整又依賴(lai)于應用各自的(de)特點,這些(xie)因素很(hen)大程度(du)上增加了GC優(you)(you)化的(de)難度(du)。

 

即(ji)便如此(ci),GC調優也(ye)不是(shi)無章可循(xun),仍然有一些(xie)通(tong)(tong)用(yong)的思考方法。本篇會介(jie)紹這些(xie)通(tong)(tong)用(yong)的GC優化策略和(he)相關實踐案例,主要包括如下(xia)內容(rong):

優(you)化前準備: 簡單回(hui)顧(gu)JVM相關知識(shi)、介紹GC優(you)化的一些通用策略。
優(you)化(hua)方(fang)法: 介紹調優(you)的(de)一般流程:明(ming)確優(you)化(hua)目標→優(you)化(hua)→跟蹤優(you)化(hua)結(jie)果。
優化案(an)(an)例: 簡述筆者所在團隊遇到的GC問題以及優化方(fang)案(an)(an)。

優化前的準(zhun)備

GC優化需知

為了更好(hao)地理解本篇所介紹的內容(rong)(rong),你需要了解如下內容(rong)(rong)。

  1. GC相關基(ji)礎知(zhi)識(shi),包括但不限于(yu):
    a) GC工作原理(li)。
    b) 理解新生代、老年(nian)代、晉(jin)升等術語(yu)含義。
    c) 可以(yi)看(kan)懂(dong)GC日志(zhi)。

  2. GC優化(hua)不能解決一切性能問題,它是最(zui)后的調優手段。

 

如果對(dui)第一(yi)點(dian)中提及(ji)的知識(shi)點(dian)不是(shi)很熟悉,可(ke)以先閱讀(du)小(xiao)結-JVM基礎(chu)回顧;如果已經很熟悉,可(ke)以跳過該節直接往下閱讀(du)。

JVM基礎回顧

JVM內存結構

簡單(dan)介紹一下JVM內存(cun)結構(gou)和常見的(de)垃圾回收(shou)器。

 

當代(dai)(dai)主流虛(xu)擬(ni)機(Hotspot VM)的(de)(de)(de)垃(la)圾回(hui)收都采用“分代(dai)(dai)回(hui)收”的(de)(de)(de)算法。“分代(dai)(dai)回(hui)收”是基于這樣一(yi)個事實(shi):對(dui)(dui)象的(de)(de)(de)生(sheng)命周期不(bu)同,所以(yi)針對(dui)(dui)不(bu)同生(sheng)命周期的(de)(de)(de)對(dui)(dui)象可以(yi)采取不(bu)同的(de)(de)(de)回(hui)收方式,以(yi)便提(ti)高回(hui)收效率。

 

Hotspot VM將(jiang)堆劃分(fen)為不(bu)同的(de)物理(li)區(qu),就是“分(fen)代(dai)(dai)”思想的(de)體(ti)現。如圖所示,JVM堆主要(yao)由新(xin)生代(dai)(dai)、老年代(dai)(dai)、永(yong)久代(dai)(dai)構成(cheng)。

 

 

  1. 新生代(Young Generation):大多數對象在新生代中被創建,其中很多對象的生命周期很短。每次新生代的垃圾回收(又稱Minor GC)后只有少量對象存活,所以選用復制算法,只需要少量的復制成本就可以完成回收。

 

  新生代(dai)內(nei)又分(fen)(fen)三個(ge)區(qu)(qu):一(yi)(yi)(yi)個(ge)Eden區(qu)(qu),兩個(ge)Survivor區(qu)(qu)(一(yi)(yi)(yi)般而(er)言),大(da)部分(fen)(fen)對(dui)象(xiang)在(zai)Eden區(qu)(qu)中生成。當Eden區(qu)(qu)滿(man)時,還(huan)存(cun)活的(de)(de)(de)對(dui)象(xiang)將被(bei)復(fu)制到兩個(ge)Survivor區(qu)(qu)(中的(de)(de)(de)一(yi)(yi)(yi)個(ge))。當這個(ge)Survivor區(qu)(qu)滿(man)時,此區(qu)(qu)的(de)(de)(de)存(cun)活且不(bu)滿(man)足(zu)“晉升”條件(jian)的(de)(de)(de)對(dui)象(xiang)將被(bei)復(fu)制到另外一(yi)(yi)(yi)個(ge)Survivor區(qu)(qu)。

 

  對(dui)象(xiang)每經歷(li)一(yi)次Minor GC,年(nian)(nian)齡(ling)(ling)加1,達到(dao)“晉(jin)升年(nian)(nian)齡(ling)(ling)閾值”后,被放到(dao)老年(nian)(nian)代(dai),這個過程(cheng)也稱為“晉(jin)升”。顯然,“晉(jin)升年(nian)(nian)齡(ling)(ling)閾值”的大小直(zhi)接(jie)影(ying)響著對(dui)象(xiang)在新生代(dai)中(zhong)的停留時間(jian),在Serial和ParNew GC兩種回收器(qi)中(zhong),“晉(jin)升年(nian)(nian)齡(ling)(ling)閾值”通過參數MaxTenuringThreshold設定,默認值為15。

 

2. 老年代(Old Generation):在新生代中經歷了N次垃圾回收后仍然存活的對象,就會被放到年老代,該區域中對象存活率高。老年代的垃圾回收(又稱Major GC)通常使用“標記-清理”或“標記-整理”算法。整堆包括新生代和老年代的垃圾回收稱為Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都會同時收集整個GC堆,包括新生代)。

 

3. 永久代(Perm Generation):主要存放元數據,例如Class、Method的元信息,與垃圾回收要回收的Java對象關系不大。相對于新生代和年老代來說,該區域的劃分對垃圾回收影響比較小。

 

常見垃圾回收器

 

不(bu)同(tong)的垃圾(ji)回收器(qi)(qi),適(shi)用于不(bu)同(tong)的場(chang)景。常用的垃圾(ji)回收器(qi)(qi):

  • 串行(xing)(Serial)回收(shou)器是單(dan)線程(cheng)的一(yi)個(ge)回收(shou)器,簡單(dan)、易實現、效率高。

  • 并行(ParNew)回(hui)收器(qi)是Serial的(de)(de)多線程(cheng)版,可以充分的(de)(de)利(li)用CPU資源(yuan),減(jian)少回(hui)收的(de)(de)時(shi)間。

  • 吞吐(tu)量優先(Parallel Scavenge)回(hui)收器,側重于吞吐(tu)量的控制。

  • 并發(fa)標(biao)記清除(CMS,Concurrent Mark Sweep)回(hui)(hui)收器(qi)(qi)是(shi)一種以獲(huo)取最短回(hui)(hui)收停頓(dun)時(shi)間為目標(biao)的(de)回(hui)(hui)收器(qi)(qi),該回(hui)(hui)收器(qi)(qi)是(shi)基(ji)于“標(biao)記-清除”算(suan)法實(shi)現的(de)。

 

GC日志

 

每一(yi)種回(hui)收(shou)(shou)器(qi)的(de)日志格(ge)式都(dou)是由其自身的(de)實現決定的(de),換而言之,每種回(hui)收(shou)(shou)器(qi)的(de)日志格(ge)式都(dou)可以不一(yi)樣。但虛擬機設計者(zhe)為了方便用戶閱讀(du),將各個回(hui)收(shou)(shou)器(qi)的(de)日志都(dou)維持一(yi)定的(de)共(gong)性。 中簡單(dan)介紹(shao)了這些共(gong)性。

 

參數基本策略

 

各分(fen)區的大(da)小(xiao)對GC的性能影響很(hen)大(da)。如何(he)將各分(fen)區調整到合適的大(da)小(xiao),分(fen)析活躍數據的大(da)小(xiao)是很(hen)好(hao)的切(qie)入點。

 

活躍(yue)(yue)數(shu)(shu)據的大(da)小(xiao)(xiao)(xiao)是(shi)指,應用程序(xu)(xu)穩定運行時(shi)長(chang)期存活對(dui)象(xiang)在堆中占用的空間大(da)小(xiao)(xiao)(xiao),也就(jiu)是(shi)Full GC后堆中老年代(dai)占用空間的大(da)小(xiao)(xiao)(xiao)。可以通過(guo)GC日(ri)志中Full GC之后老年代(dai)數(shu)(shu)據大(da)小(xiao)(xiao)(xiao)得出(chu),比較準確的方(fang)法是(shi)在程序(xu)(xu)穩定后,多次獲(huo)取(qu)GC數(shu)(shu)據,通過(guo)取(qu)平均值的方(fang)式計(ji)算活躍(yue)(yue)數(shu)(shu)據的大(da)小(xiao)(xiao)(xiao)。活躍(yue)(yue)數(shu)(shu)據和(he)各(ge)分(fen)區之間的比例關系(xi)如下(xia)(見參考文(wen)獻1):

 

例如(ru),根據GC日(ri)志獲得老年代的活(huo)躍數據大(da)小(xiao)為(wei)300MB,那么各(ge)分區大(da)小(xiao)可以(yi)設為(wei):

總堆(dui):1200MB = 300MB × 4
新生代:450MB = 300MB × 1.5
老(lao)年代: 750MB = 1200MB - 450MB*

這(zhe)部分設置僅(jin)僅(jin)是堆大小(xiao)的(de)初始值(zhi),后(hou)面(mian)的(de)優(you)化(hua)中(zhong),可(ke)能(neng)會(hui)調整這(zhe)些值(zhi),具(ju)體情況取決于應(ying)用程序(xu)的(de)特性和(he)需(xu)求。

 

優化步(bu)驟

GC優化一般步驟可以(yi)概括為:確定目標、優化參(can)數、驗(yan)收結果。

確定目標

明確應(ying)用(yong)程(cheng)序的系統(tong)需求是性能優(you)化的基礎,系統(tong)的需求是指應(ying)用(yong)程(cheng)序運(yun)行時某方面的要求,譬如:

  • 高可用(yong),可用(yong)性達到(dao)幾個9。

  • 低延遲(chi),請求必(bi)須多少毫秒內(nei)完(wan)成響應。

  • 高吞吐,每秒完成(cheng)多少次事務。

 

明確系統需求之所以重要,是因為上述性能指標間(jian)可能沖(chong)突。比(bi)如通常情況(kuang)下(xia),縮小(xiao)延遲的(de)(de)代(dai)價(jia)是降低(di)吞(tun)吐(tu)量或者(zhe)消耗更多的(de)(de)內存或者(zhe)兩(liang)者(zhe)同時發生。

 

由于(yu)筆者所(suo)在團(tuan)隊主要關(guan)注高可(ke)用和低延遲兩(liang)項指標,所(suo)以接下來分析,如何量化GC時間和頻率對于(yu)響(xiang)(xiang)應時間和可(ke)用性的(de)影響(xiang)(xiang)。通過(guo)這個量化指標,可(ke)以計算(suan)出(chu)當前(qian)GC情況對服務的(de)影響(xiang)(xiang),也能(neng)評估出(chu)GC優化后對響(xiang)(xiang)應時間的(de)收益,這兩(liang)點(dian)對于(yu)低延遲服務很(hen)重要。

 

舉例:假設單(dan)位時間T內(nei)發生一次持(chi)續25ms的GC,接口平均響(xiang)應時間為50ms,且請求均勻到達,根(gen)據下圖所示:

那么有(50ms+25ms)/T比(bi)例的(de)請求會(hui)受GC影響,其(qi)中GC前的(de)50ms內到達的(de)請求都(dou)(dou)會(hui)增(zeng)加25ms,GC期間(jian)的(de)25ms內到達的(de)請求,會(hui)增(zeng)加0-25ms不(bu)等,如果時(shi)間(jian)T內發生N次GC,受GC影響請求占(zhan)比(bi)=(接口響應(ying)時(shi)間(jian)+GC時(shi)間(jian))×N/T 。可(ke)見(jian)無論降低單次GC時(shi)間(jian)還(huan)是降低GC次數N都(dou)(dou)可(ke)以有效減少GC對響應(ying)時(shi)間(jian)的(de)影響。

 

優化

通過收集GC信息,結(jie)合系統需求,確定(ding)優化方案(an),例如(ru)選用合適的GC回(hui)收器(qi)、重新設置內存比例、調整JVM參數等。

 

進(jin)行調整后,將不同的優化方(fang)案分別(bie)應用到多臺機(ji)器(qi)(qi)上,然(ran)后比(bi)較這些機(ji)器(qi)(qi)上GC的性能差異,有針對性的做出選擇,再通(tong)過不斷(duan)的試(shi)驗(yan)和(he)觀察,找(zhao)到最合適的參數。

驗收優化結果

將修改應(ying)用到所有服務器,判斷優(you)化結(jie)(jie)果是否符合預期,總結(jie)(jie)相關(guan)經驗。

接下來,我(wo)們通過三(san)個(ge)案(an)例來實踐以上的優化流程(cheng)和基本原則(本文中三(san)個(ge)案(an)例使用的垃(la)圾(ji)回(hui)收器均(jun)為ParNew+CMS,CMS失敗(bai)時Serial Old替補(bu))。

GC優化案(an)例

案例一:Major GC和Minor GC頻繁

確定目標

服務情(qing)況(kuang):Minor GC每分鐘(zhong)100次(ci) ,Major GC每4分鐘(zhong)一(yi)次(ci),單(dan)次(ci)Minor GC耗(hao)(hao)時25ms,單(dan)次(ci)Major GC耗(hao)(hao)時200ms,接口響應時間50ms。

 

由于這個服(fu)務(wu)要求(qiu)低延時高可(ke)(ke)用,結合上(shang)文中(zhong)提到的(de)GC對服(fu)務(wu)響(xiang)應(ying)(ying)時間的(de)影響(xiang),計算可(ke)(ke)知由于Minor GC的(de)發(fa)生,12.5%的(de)請求(qiu)響(xiang)應(ying)(ying)時間會增加,其中(zhong)8.3%的(de)請求(qiu)響(xiang)應(ying)(ying)時間會增加25ms,可(ke)(ke)見當前GC情況對響(xiang)應(ying)(ying)時間影響(xiang)較(jiao)大。

 

(50ms+25ms)× 100次/60000ms = 12.5%,50ms × 100次/60000ms = 8.3% 。

 

優化目標:降低(di)TP99、TP90時間。

 

優化

 

首(shou)先(xian)優化Minor GC頻(pin)(pin)繁問題。通常情況下,由于新生(sheng)代(dai)空間較小,Eden區很快被(bei)填滿,就(jiu)會導致頻(pin)(pin)繁Minor GC,因(yin)此可以通過(guo)增(zeng)(zeng)大新生(sheng)代(dai)空間來降低Minor GC的(de)頻(pin)(pin)率。例如在相同的(de)內存分配率的(de)前提下,新生(sheng)代(dai)中(zhong)的(de)Eden區增(zeng)(zeng)加一倍(bei),Minor GC的(de)次數就(jiu)會減少一半。

 

這時(shi)很(hen)多人有(you)這樣的(de)疑問,擴容Eden區雖(sui)然可以減少Minor GC的(de)次(ci)(ci)數(shu),但(dan)會(hui)增加(jia)(jia)單次(ci)(ci)Minor GC時(shi)間(jian)么(me)?根據上面(mian)公(gong)式,如(ru)果(guo)單次(ci)(ci)Minor GC時(shi)間(jian)也(ye)增加(jia)(jia),很(hen)難保證(zheng)最后的(de)優(you)化效(xiao)果(guo)。我們結合(he)下面(mian)情況來分析,單次(ci)(ci)Minor GC時(shi)間(jian)主要受哪些因(yin)素影響?是否和新(xin)生代大小存在(zai)線性(xing)關(guan)系(xi)?

首先,單(dan)次Minor GC時(shi)間由以(yi)下(xia)(xia)兩部分組成:T1(掃(sao)描(miao)(miao)新(xin)生代(dai))和 T2(復制存(cun)活對象到Survivor區)如下(xia)(xia)圖。(注:這里(li)為了簡(jian)化問題,我們(men)認(ren)為T1只掃(sao)描(miao)(miao)新(xin)生代(dai)判(pan)斷(duan)對象是否存(cun)活的時(shi)間,其實該階段(duan)還需要掃(sao)描(miao)(miao)部分老(lao)年代(dai),后面案例中有(you)詳細(xi)描(miao)(miao)述。)

 

  • 擴容前(qian):新(xin)生(sheng)(sheng)代(dai)容量(liang)為(wei)R ,假設對(dui)象(xiang)A的存活(huo)時間(jian)為(wei)750ms,Minor GC間(jian)隔500ms,那么(me)本(ben)次Minor GC時間(jian)= T1(掃描新(xin)生(sheng)(sheng)代(dai)R)+T2(復制對(dui)象(xiang)A到S)。

  • 擴容后:新生(sheng)代(dai)容量為(wei)2R ,對象(xiang)A的生(sheng)命(ming)周(zhou)期為(wei)750ms,那么Minor GC間隔增加為(wei)1000ms,此時(shi)Minor GC對象(xiang)A已不再存活,不需要把它復(fu)制到Survivor區(qu),那么本次GC時(shi)間 = 2 × T1(掃(sao)描新生(sheng)代(dai)R),沒(mei)有T2復(fu)制時(shi)間。

 

可見,擴(kuo)容(rong)后,Minor GC時增加了T1(掃描(miao)時間(jian)),但省去(qu)T2(復制對(dui)(dui)象)的(de)時間(jian),更(geng)重(zhong)要(yao)的(de)是對(dui)(dui)于虛擬機來說,復制對(dui)(dui)象的(de)成(cheng)本要(yao)遠(yuan)高于掃描(miao)成(cheng)本,所以,單(dan)次Minor GC時間(jian)更(geng)多(duo)取(qu)決(jue)于GC后存活對(dui)(dui)象的(de)數(shu)量,而(er)非Eden區的(de)大小(xiao)。因此如果堆(dui)中(zhong)短期(qi)對(dui)(dui)象很多(duo),那么擴(kuo)容(rong)新生代(dai),單(dan)次Minor GC時間(jian)不(bu)會顯著(zhu)增加。下面(mian)需要(yao)確認(ren)下服務(wu)中(zhong)對(dui)(dui)象的(de)生命周期(qi)分布情況:

 

 

通過上圖GC日(ri)志中兩處紅色框標記內容可(ke)知:

  1. new threshold = 2(動態年(nian)(nian)齡(ling)判(pan)斷,對象的晉升(sheng)年(nian)(nian)齡(ling)閾值為(wei)2),對象僅經(jing)歷2次Minor GC后就(jiu)晉升(sheng)到老(lao)年(nian)(nian)代,這樣老(lao)年(nian)(nian)代會迅速(su)被填滿,直接導致了頻(pin)繁的Major GC。

  2. Major GC后(hou)老(lao)年代使用空間為300Mb+,意味著(zhu)此時(shi)絕大多(duo)數(86% = 2G/2.3G)的對(dui)象已經不再存活,也就是說生(sheng)命周期長(chang)的對(dui)象占比很小。

 

由此可見,服務中存在大量短期臨時對(dui)象(xiang),擴容新生代空(kong)間后,Minor GC頻率降(jiang)低,對(dui)象(xiang)在新生代得到充分回收,只(zhi)有生命周(zhou)期長(chang)的對(dui)象(xiang)才(cai)進入老年(nian)代。這樣(yang)老年(nian)代增速(su)變慢(man),Major GC頻率自然也會降(jiang)低。

 

優化結果

 

通過擴(kuo)容新(xin)生代為(wei)為(wei)原來(lai)的三倍,單(dan)次Minor GC時間增加小于5ms,頻率下降了60%,服務響(xiang)應(ying)時間TP90,TP99都下降了10ms+,服務可用性得到提(ti)升(sheng)。

 

調整前:

 

 

調整后:

 

 

小結

 

如(ru)何選(xuan)擇(ze)各分區(qu)大小應該(gai)依賴(lai)應用(yong)程序中對象(xiang)生命(ming)周期的(de)分布情況:如(ru)果應用(yong)存(cun)在(zai)大量的(de)短期對象(xiang),應該(gai)選(xuan)擇(ze)較大的(de)年(nian)輕代(dai)(dai);如(ru)果存(cun)在(zai)相(xiang)對較多的(de)持(chi)久(jiu)對象(xiang),老年(nian)代(dai)(dai)應該(gai)適(shi)當增大。

 

更多思考

 

關于上文中提到(dao)晉升(sheng)年齡(ling)閾值為2,很多同學有疑問,為什么設置了MaxTenuringThreshold=15,對象仍然僅經歷2次Minor GC,就晉升(sheng)到(dao)老年代?這里(li)涉及(ji)到(dao)“動態年齡(ling)計算”的概(gai)念。

 

動態(tai)年(nian)(nian)齡(ling)計(ji)算:Hotspot遍歷所有對象時,按(an)照年(nian)(nian)齡(ling)從(cong)小(xiao)(xiao)到大(da)(da)對其所占(zhan)用的大(da)(da)小(xiao)(xiao)進行累積,當累積的某個年(nian)(nian)齡(ling)大(da)(da)小(xiao)(xiao)超過(guo)了survivor區(qu)的一(yi)(yi)半(ban)時,取(qu)這個年(nian)(nian)齡(ling)和MaxTenuringThreshold中(zhong)更(geng)小(xiao)(xiao)的一(yi)(yi)個值,作為新的晉(jin)升年(nian)(nian)齡(ling)閾值。在本案例中(zhong),調(diao)優前:Survivor區(qu) = 64M,desired survivor = 32M,此時Survivor區(qu)中(zhong)age<=2的對象累計(ji)大(da)(da)小(xiao)(xiao)為41M,41M大(da)(da)于(yu)32M,所以晉(jin)升年(nian)(nian)齡(ling)閾值被設置為2,下次Minor GC時將年(nian)(nian)齡(ling)超過(guo)2的對象被晉(jin)升到老年(nian)(nian)代。

 

JVM引(yin)入動態年(nian)齡計算,主要基(ji)于如下兩(liang)點考慮:

  1. 如(ru)果(guo)固定按照MaxTenuringThreshold設定的閾值作為晉(jin)升條(tiao)件(jian):
    a)MaxTenuringThreshold設置(zhi)的(de)過大(da),原(yuan)本應(ying)該晉升的(de)對象一(yi)直停留在Survivor區,直到(dao)Survivor區溢出,一(yi)旦溢出發生,Eden+Svuvivor中對象將(jiang)不(bu)再依據年齡全部提(ti)升到(dao)老年代,這樣對象老化(hua)的(de)機制就失效了。
    b)MaxTenuringThreshold設置的過小,“過早晉升”即對象(xiang)不能在(zai)新生(sheng)代(dai)充分被(bei)回收,大量短期對象(xiang)被(bei)晉升到老年(nian)代(dai),老年(nian)代(dai)空(kong)間迅速(su)增(zeng)長(chang),引(yin)起頻繁的Major GC。分代(dai)回收失去了意義,嚴(yan)重(zhong)影響GC性能。

  2. 相同(tong)應用在不同(tong)時間(jian)的(de)(de)表(biao)現不同(tong):特殊任(ren)務(wu)的(de)(de)執行(xing)或者流量成分的(de)(de)變化(hua),都會導致(zhi)對象的(de)(de)生(sheng)命周期(qi)分布(bu)發(fa)生(sheng)波(bo)動,那么固(gu)定的(de)(de)閾值設(she)定,因為無法動態適應變化(hua),會造(zao)成和上面相同(tong)的(de)(de)問(wen)題。

 

總結來(lai)說(shuo),為了更好的(de)適應不同程序的(de)內存情況,虛(xu)擬機并不總是要(yao)求對象年(nian)齡必須達(da)到(dao)Maxtenuringthreshhold再晉級老年(nian)代(dai)。

 

案例二:請求高峰期發生GC,導致服務可用性下降

 

確定目標

GC日志顯示,高峰期(qi)CMS在重(zhong)標記(Remark)階段(duan)耗時(shi)1.39s。Remark階段(duan)是Stop-The-World(以(yi)下簡稱為(wei)STW)的,即(ji)在執(zhi)行垃圾回收(shou)時(shi),Java應用程(cheng)(cheng)序中除(chu)了垃圾回收(shou)器(qi)線(xian)(xian)程(cheng)(cheng)之外其他所有線(xian)(xian)程(cheng)(cheng)都被(bei)掛起(qi),意味(wei)著在此期(qi)間(jian)(jian),用戶正常工(gong)作的線(xian)(xian)程(cheng)(cheng)全部被(bei)暫(zan)停下來,這是低(di)延時(shi)服務不能接受的。本(ben)次優化目標是降低(di)Remark時(shi)間(jian)(jian)。

 

 

優化

 

解決問題(ti)前,先(xian)回(hui)顧一下(xia)CMS的(de)四個(ge)主要階段,以(yi)及(ji)各個(ge)階段的(de)工作(zuo)內容。下(xia)圖展(zhan)示了CMS各個(ge)階段可以(yi)標記的(de)對象,用不同(tong)顏色(se)區(qu)分(fen)。

 

  1. Init-mark初始標記(STW) ,該階段進行可達性分析,標記GC ROOT能直接關聯到的對象,所以(yi)很快。

  2. Concurrent-mark并發(fa)標記,由(you)前階段標記過的綠色對象(xiang)出發(fa),所有(you)可到達的對象(xiang)都在本(ben)階段中標記。

  3. Remark重標(biao)(biao)記(ji)(ji)(STW) ,暫(zan)停所有用(yong)戶(hu)線(xian)程,重新掃描堆中(zhong)的(de)(de)(de)對(dui)(dui)(dui)(dui)象(xiang)(xiang)(xiang),進行(xing)可(ke)達性(xing)分析,標(biao)(biao)記(ji)(ji)活(huo)著的(de)(de)(de)對(dui)(dui)(dui)(dui)象(xiang)(xiang)(xiang)。因(yin)為(wei)并發(fa)標(biao)(biao)記(ji)(ji)階(jie)段(duan)是和用(yong)戶(hu)線(xian)程并發(fa)執行(xing)的(de)(de)(de)過程,所以(yi)該(gai)過程中(zhong)可(ke)能有用(yong)戶(hu)線(xian)程修改(gai)某些(xie)活(huo)躍對(dui)(dui)(dui)(dui)象(xiang)(xiang)(xiang)的(de)(de)(de)字段(duan),指向了一個(ge)未(wei)標(biao)(biao)記(ji)(ji)過的(de)(de)(de)對(dui)(dui)(dui)(dui)象(xiang)(xiang)(xiang),如(ru)下(xia)(xia)圖中(zhong)紅色對(dui)(dui)(dui)(dui)象(xiang)(xiang)(xiang)在并發(fa)標(biao)(biao)記(ji)(ji)開始時不可(ke)達,但是并行(xing)期(qi)間引用(yong)發(fa)生(sheng)變化,變為(wei)對(dui)(dui)(dui)(dui)象(xiang)(xiang)(xiang)可(ke)達,這個(ge)階(jie)段(duan)需要重新標(biao)(biao)記(ji)(ji)出此類對(dui)(dui)(dui)(dui)象(xiang)(xiang)(xiang),防止(zhi)在下(xia)(xia)一階(jie)段(duan)被清理掉,這個(ge)過程也是需要STW的(de)(de)(de)。特(te)別需要注(zhu)意(yi)一點,這個(ge)階(jie)段(duan)是以(yi)新生(sheng)代中(zhong)對(dui)(dui)(dui)(dui)象(xiang)(xiang)(xiang)為(wei)根來判斷對(dui)(dui)(dui)(dui)象(xiang)(xiang)(xiang)是否存活(huo)的(de)(de)(de)。

  4. 并(bing)發(fa)清理(li)(li),進行(xing)并(bing)發(fa)的垃圾清理(li)(li)。

 

可見,Remark階段(duan)主要是(shi)(shi)通過掃描(miao)堆來判(pan)斷對(dui)(dui)象是(shi)(shi)否(fou)存(cun)活(huo)。那么準確判(pan)斷對(dui)(dui)象是(shi)(shi)否(fou)存(cun)活(huo),需要掃描(miao)哪些對(dui)(dui)象?CMS對(dui)(dui)老(lao)年(nian)代做回收(shou),Remark階段(duan)僅掃描(miao)老(lao)年(nian)代是(shi)(shi)否(fou)可行?結論(lun)是(shi)(shi)不(bu)可行,原因如下:

 

如果(guo)僅掃描(miao)老年代(dai)中對(dui)象(xiang),即以老年代(dai)中對(dui)象(xiang)為根,判(pan)斷對(dui)象(xiang)是否存在(zai)(zai)引(yin)用,上圖中,對(dui)象(xiang)A因(yin)為引(yin)用存在(zai)(zai)新(xin)生代(dai)中,它在(zai)(zai)Remark階(jie)段就不會被修正標記(ji)為可達,GC時會被錯誤回收(shou)。

新生代(dai)對(dui)(dui)象(xiang)持有老年代(dai)中(zhong)對(dui)(dui)象(xiang)的引用(yong),這種情況(kuang)稱為“跨代(dai)引用(yong)”。因它的存在,Remark階段必須(xu)掃描整個堆(dui)來判斷對(dui)(dui)象(xiang)是否存活,包(bao)括圖中(zhong)灰色的不可達對(dui)(dui)象(xiang)。

 

灰色對(dui)象已(yi)經不可(ke)達(da),但仍然需要(yao)掃描的原(yuan)因:新(xin)生代(dai)GC和老(lao)(lao)年代(dai)的GC是(shi)各自分開獨立進(jin)行的,只有Minor GC時(shi)才會使(shi)用根搜(sou)索算法,標記新(xin)生代(dai)對(dui)象是(shi)否可(ke)達(da),也就是(shi)說雖然一些(xie)(xie)對(dui)象已(yi)經不可(ke)達(da),但在Minor GC發生前不會被標記為不可(ke)達(da),CMS也無法辨認哪(na)些(xie)(xie)對(dui)象存活,只能全堆(dui)掃描(新(xin)生代(dai)+老(lao)(lao)年代(dai))。由此(ci)可(ke)見堆(dui)中對(dui)象的數目影響了Remark階段(duan)耗時(shi)。

分析GC日志可以(yi)得出同樣(yang)的(de)規律,Remark耗時(shi)>500ms時(shi),新(xin)生代使用率都(dou)在(zai)75%以(yi)上(shang)。這樣(yang)降低(di)Remark階(jie)段耗時(shi)問題轉換成如何減少(shao)新(xin)生代對象數量。

 

新生代中(zhong)對(dui)象(xiang)(xiang)的(de)(de)特點是“朝(chao)生夕滅”,這樣如果Remark前執行一(yi)次Minor GC,大部分對(dui)象(xiang)(xiang)就會被(bei)回收。CMS就采用(yong)(yong)了這樣的(de)(de)方式,在(zai)Remark前增加(jia)了一(yi)個可中(zhong)斷(duan)的(de)(de)并發預清理(CMS-concurrent-abortable-preclean),該階段(duan)主要工作仍然是并發標記(ji)對(dui)象(xiang)(xiang)是否存活(huo),只是這個過(guo)程可被(bei)中(zhong)斷(duan)。此階段(duan)在(zai)Eden區使用(yong)(yong)超過(guo)2Mb時啟動,直到Eden區空間使用(yong)(yong)率達到50%時中(zhong)斷(duan),當(dang)然2Mb和50%都是默認的(de)(de)閾值(zhi),可以通過(guo)參數修(xiu)改(gai)。如果此階段(duan)執行時等到了Minor GC,那么上述(shu)灰色對(dui)象(xiang)(xiang)將被(bei)回收,Reamark階段(duan)需(xu)要掃描的(de)(de)對(dui)象(xiang)(xiang)就少了。

 

除此(ci)之外CMS為了避(bi)免這個(ge)階段沒(mei)有(you)等到Minor GC而(er)陷(xian)入(ru)無限等待,提(ti)供(gong)了參數CMSMaxAbortablePrecleanTime ,默(mo)認(ren)為5s,含義是(shi)如果可(ke)中斷(duan)的預清理執行超過5s,不管發沒(mei)發生Minor GC,都會中止此(ci)階段,進(jin)入(ru)Remark。

根據GC日志紅(hong)色標(biao)記2處顯示,可中斷(duan)的并發預清理(li)執(zhi)行了(le)5.35s,超過了(le)設置的5s被中斷(duan),期間沒有(you)等到(dao)Minor GC ,所以Remark時新生代中仍然有(you)很多對象。

 

對于這(zhe)種情況,CMS提供(gong)CMSScavengeBeforeRemark參數,用(yong)來保證Remark前強制進行一次Minor GC。

 

優化結果

 

經過增加CMSScavengeBeforeRemark參數(shu),單次執行時(shi)間>200ms的GC停頓消失,從監控上觀察(cha),GCtime和業務波動保(bao)持一致,不再有(you)明顯的毛刺。

 

 

 

小結

 

通過案例分析(xi)了解到(dao),由于跨代(dai)引用(yong)的(de)(de)存(cun)在,CMS在Remark階(jie)(jie)段必須掃描(miao)(miao)整個(ge)堆,同(tong)時(shi)(shi)為了避免(mian)掃描(miao)(miao)時(shi)(shi)新(xin)生(sheng)代(dai)有(you)很多對象,增加了可中斷的(de)(de)預清理階(jie)(jie)段用(yong)來(lai)等待Minor GC的(de)(de)發生(sheng)。只是(shi)該(gai)階(jie)(jie)段有(you)時(shi)(shi)間限制(zhi),如果超時(shi)(shi)等不到(dao)Minor GC,Remark時(shi)(shi)新(xin)生(sheng)代(dai)仍然有(you)很多對象,我(wo)們(men)的(de)(de)調優策略是(shi),通過參數(shu)強制(zhi)Remark前進(jin)行一次Minor GC,從而降(jiang)低Remark階(jie)(jie)段的(de)(de)時(shi)(shi)間。

 

更多思考

 

案(an)例中(zhong)只涉及老年(nian)代(dai)GC,其實新生代(dai)GC存在同樣(yang)的問題,即老年(nian)代(dai)可能持有新生代(dai)對象引(yin)用,所以Minor GC時也必(bi)須掃描老年(nian)代(dai)。

 

JVM是如何(he)避免Minor GC時掃(sao)描全(quan)堆的(de)?

經(jing)過(guo)統計(ji)信息顯示,老年代(dai)持有(you)新生(sheng)代(dai)對象引用(yong)的情況不足1%,根據這(zhe)一特性(xing)JVM引入了卡(ka)表(card table)來實現這(zhe)一目的。如下圖所(suo)示:

 

卡(ka)表的(de)具體策(ce)略是將老(lao)年代(dai)(dai)的(de)空間分成大小為512B的(de)若干張(zhang)卡(ka)(card)。卡(ka)表本(ben)身(shen)是單(dan)字節(jie)數組(zu),數組(zu)中(zhong)的(de)每(mei)個元素對應著一張(zhang)卡(ka),當發生(sheng)老(lao)年代(dai)(dai)引用(yong)新生(sheng)代(dai)(dai)時(shi)(shi),虛擬機將該卡(ka)對應的(de)卡(ka)表元素設置為適當的(de)值。如(ru)上圖(tu)所示,卡(ka)表3被(bei)標記為臟(zang)(卡(ka)表還有另外的(de)作(zuo)用(yong),標識并發標記階段哪(na)些塊被(bei)修改(gai)過(guo)),之(zhi)后Minor GC時(shi)(shi)通(tong)過(guo)掃(sao)描卡(ka)表就(jiu)可以(yi)很(hen)快的(de)識別哪(na)些卡(ka)中(zhong)存在(zai)老(lao)年代(dai)(dai)指向新生(sheng)代(dai)(dai)的(de)引用(yong)。這樣虛擬機通(tong)過(guo)空間換時(shi)(shi)間的(de)方式,避免了全(quan)堆掃(sao)描。

 

總結來說,CMS的(de)(de)設(she)計聚焦在(zai)獲取最短的(de)(de)時(shi)延,為此它“不遺余力”地(di)做了很多工作(zuo),包括盡量讓應用程(cheng)序(xu)和GC線(xian)程(cheng)并發(fa)、增加可中斷(duan)的(de)(de)并發(fa)預清理(li)階段、引入卡表等,雖(sui)然這些(xie)操(cao)作(zuo)犧牲了一定吞吐量但(dan)獲得了更短的(de)(de)回收停(ting)頓時(shi)間。

 

主案例三:發生Stop-The-World的GC

 

確定目標

GC日(ri)志(zhi)如下圖(在GC日(ri)志(zhi)中,Full GC是(shi)用(yong)(yong)來說明(ming)這(zhe)次(ci)垃(la)圾回收的停頓類(lei)(lei)型,代表(biao)STW類(lei)(lei)型的GC,并(bing)不特(te)指(zhi)老(lao)年代GC),根據(ju)GC日(ri)志(zhi)可知本次(ci)Full GC耗時1.23s。這(zhe)個在線服務同樣(yang)要求低時延高可用(yong)(yong)。本次(ci)優(you)化目(mu)標是(shi)降低單(dan)次(ci)STW回收停頓時間,提高可用(yong)(yong)性(xing)。

 

 

優化

首(shou)先,什(shen)么(me)時候(hou)可能會觸發STW的(de)Full GC呢?

  1. Perm空間不足(zu);

  2. CMS GC時出(chu)現promotion failed和concurrent mode failure(concurrent mode failure發生的(de)(de)原因(yin)一般是(shi)CMS正在進行,但(dan)是(shi)由于老(lao)年代(dai)空(kong)間不足,需要盡(jin)快回收老(lao)年代(dai)里面的(de)(de)不再(zai)被使用的(de)(de)對象(xiang),這時停止所有的(de)(de)線(xian)程,同時終止CMS,直接進行Serial Old GC);

  3. 統計得到(dao)的(de)Young GC晉升到(dao)老年(nian)代的(de)平均大(da)(da)小大(da)(da)于老年(nian)代的(de)剩余空間(jian);

  4. 主動觸發(fa)Full GC(執行jmap -histo:live [pid])來(lai)避免(mian)碎(sui)片(pian)問題(ti)。

 

然后(hou),我(wo)們來(lai)逐一分(fen)析一下:

  • 排(pai)除原因2:如果是原因2中兩種(zhong)情況,日志(zhi)中會有特殊標識(shi),目前沒有。

  • 排(pai)除原(yuan)因3:根據GC日志,當(dang)時老(lao)年(nian)代使用量僅為20%,也(ye)不(bu)存在(zai)大(da)于(yu)2G的大(da)對象產生(sheng)。

  • 排除原因(yin)4:因(yin)為當時沒有相關(guan)命令(ling)執行。

  • 鎖定原因1:根據日志發現(xian)Full GC后,Perm區變大了(le),推斷是由(you)于(yu)永久帶空間不足(zu)容(rong)量擴展導(dao)致(zhi)的(de)。

 

找到原因(yin)后解決(jue)方法有兩種(zhong):

  1. 通過把(ba)-XX:PermSize參數和-XX:MaxPermSize設置成一樣,強制虛擬(ni)機在啟動(dong)的時候就把(ba)永(yong)久帶的容量固定下來,避免運(yun)行時自動(dong)擴容。

  2. CMS默(mo)認(ren)情況下不會回收(shou)Perm區,通過參數CMSPermGenSweepingEnabled、CMSClassUnloadingEnabled ,可以讓CMS在Perm區容量不足(zu)時對其回收(shou)。

由于該服務沒(mei)有生成大量動(dong)態類,回收Perm區收益(yi)不大,所以我們采(cai)用方案1,啟動(dong)時將Perm區大小(xiao)固(gu)定,避(bi)免進行動(dong)態擴容。

 

優化結果

調整參數后,服務(wu)不再有(you)Perm區擴(kuo)容導(dao)致的STW GC發(fa)生。

 

小結

對于性能要求很高的(de)服務,建議將(jiang)MaxPermSize和MinPermSize設(she)置(zhi)成一(yi)致(JDK8開始,Perm區完(wan)全(quan)消失,轉(zhuan)而使(shi)用(yong)元空(kong)(kong)間。而元空(kong)(kong)間是直接(jie)存(cun)(cun)(cun)(cun)(cun)在(zai)內存(cun)(cun)(cun)(cun)(cun)中(zhong),不在(zai)JVM中(zhong)),Xms和Xmx也設(she)置(zhi)為(wei)相(xiang)同,這樣可以減少內存(cun)(cun)(cun)(cun)(cun)自動(dong)擴容(rong)和收縮(suo)帶來的(de)性能損失。虛擬機(ji)啟動(dong)的(de)時候(hou)就會(hui)把參數中(zhong)所設(she)定的(de)內存(cun)(cun)(cun)(cun)(cun)全(quan)部化為(wei)私(si)有,即(ji)使(shi)擴容(rong)前有一(yi)部分內存(cun)(cun)(cun)(cun)(cun)不會(hui)被用(yong)戶代碼用(yong)到,這部分內存(cun)(cun)(cun)(cun)(cun)在(zai)虛擬機(ji)中(zhong)被標識為(wei)虛擬內存(cun)(cun)(cun)(cun)(cun),也不會(hui)交給其(qi)他進(jin)程使(shi)用(yong)。

 

總結

結合上(shang)述GC優化(hua)案例做個總(zong)結:

  1. 首先再次聲明(ming),在(zai)進行GC優(you)化之(zhi)前,需(xu)要確認(ren)項目的架構(gou)和(he)代碼等已經沒有(you)優(you)化空間。我們(men)不(bu)能(neng)(neng)指望一個(ge)系統架構(gou)有(you)缺陷(xian)或者代碼層次優(you)化沒有(you)窮(qiong)盡的應(ying)用,通過(guo)GC優(you)化令其性能(neng)(neng)達到一個(ge)質的飛躍。

  2. 其(qi)次,通過上(shang)述分析,可(ke)以看出(chu)虛(xu)擬機內部已有很多優化來保證應用的(de)穩定運行,所以不(bu)要為了調優而調優,不(bu)當的(de)調優可(ke)能適得其(qi)反。

  3. 最后,GC優化(hua)是一個(ge)系統而(er)復雜的(de)(de)工作,沒有(you)萬(wan)能的(de)(de)調優策略可以(yi)滿足所(suo)有(you)的(de)(de)性能指(zhi)標。GC優化(hua)必須建立在我們深入理解各種垃(la)圾回收(shou)器的(de)(de)基礎上,才能有(you)事半功倍的(de)(de)效果。

參考(kao)文(wen)獻

  1. Scott O. Java Performance:The Definitive Guide. O'Reilly, 2014.

  2. 周志明,深入(ru)理(li)解Java虛擬機[M],機械工業出版社,2013.

  3. CMS垃圾回收機制.

posted @ 2014-11-27 14:58  ^_TONY_^  閱讀(2516)  評論(0)    收藏  舉報