java的前生今世_HBaseGC的前生今世-身世篇
網(wǎng)易視頻云是網(wǎng)易傾力打造的一款基于云計算的分布式多媒體處理集群和專業(yè)音視頻技術(shù),提供穩(wěn)定流暢、低時延、高并發(fā)的視頻直播、錄制、存儲、轉(zhuǎn)碼及點播等音視頻的PAAS服務(wù),在線教育、遠(yuǎn)程醫(yī)療、娛樂秀
網(wǎng)易視頻云是網(wǎng)易傾力打造的一款基于云計算的分布式多媒體處理集群和專業(yè)音視頻技術(shù),提供穩(wěn)定流暢、低時延、高并發(fā)的視頻直播、錄制、存儲、轉(zhuǎn)碼及點播等音視頻的PAAS服務(wù),在線教育、遠(yuǎn)程醫(yī)療、娛樂秀場、在線金融等各行業(yè)及企業(yè)用戶只需經(jīng)過簡單的開發(fā)即可打造在線音視頻平臺。現(xiàn)在,網(wǎng)易視頻云的技術(shù)專家給大家分享一則技術(shù)文:HBase GC的前生今世 - 身世篇。
在之前的HBase BlockCache系列文章中已經(jīng)簡單提到:使用LRUBlockCache緩存機(jī)制會因為CMS GC策略導(dǎo)致內(nèi)存碎片過多,從而可能引發(fā)臭名昭著的Full GC,觸發(fā)可怕的’stop-the-world’暫停,嚴(yán)重影響上層業(yè)務(wù);而Bucket Cache緩存機(jī)制因為在初始化的時候就申請了一片固定大小的內(nèi)存作為緩存,緩存淘汰不再由 JVM管理,數(shù)據(jù)Block的緩存操作只是對這篇空間的訪問和覆蓋,因而大大減少了內(nèi)存碎片的出現(xiàn),降低了Full GC發(fā)生的頻率。那CMS
GC策略如何導(dǎo)致內(nèi)存碎片過多?內(nèi)存碎片過多如何觸發(fā)Full GC?HBase在演進(jìn)的道路上又如何不斷優(yōu)化CMS GC?接下來這個系列《HBase GC的前生今生》將會為你一一揭開謎底,這個系列一共兩篇文章,本篇文章-’身世篇’將會帶你全面了解HBase的GC機(jī)制,后面一篇-’演進(jìn)篇’將會給你道出HBase在發(fā)展的道路上如何不斷對Full GC進(jìn)行優(yōu)化。
Java GC概述
整個HBase是構(gòu)建在JVM虛擬機(jī)上的,因此了解HBase的內(nèi)存管理機(jī)制以及不同緩存機(jī)制對GC的影響,就必須對Java GC有一個全面的了解。至于深入地理解Java GC 的工作原理,不在本文的討論范圍之內(nèi);當(dāng)然,如果已經(jīng)對Java GC比較熟悉,也可以跳過此節(jié)。
Java GC建立在這樣一個假設(shè)基礎(chǔ)上的:大多數(shù)內(nèi)存對象要么生存周期比較短,很快就會沒人引用,比如處理RPC請求的buffer可能只會生存幾微秒;要么生存周期比較長,比如Block Cache中的熱點Block,可能就會生存幾分鐘,甚至更長時間。基于這樣的事實,JVM將整個堆內(nèi)存分為兩個部分:新生代(young generation)和老生代(tenured generation),除此之外,JVM還有一個非堆內(nèi)存區(qū)-Perm區(qū),主要存放class信息以及其他meta元信息,內(nèi)存結(jié)構(gòu)如下圖所示:
其中Young區(qū)又分為Eden區(qū)和兩個Survivor 區(qū):S0和S1。一個內(nèi)存對象在創(chuàng)建之后,首先會為其在新生代申請一塊內(nèi)存空間,如果這個對象在新生代存活了很長時間,會將其遷移到老生代。 在大多數(shù)對延遲敏感的業(yè)務(wù)場景下(比如HBase),建議使用如下JVM參數(shù),-XX:+UseParNewGC和XX:+UseConcMarkSweepGC,其中前者表示對新生代執(zhí)行并行的垃圾回收機(jī)制,而后者表示對老生代執(zhí)行并行標(biāo)記-清除垃圾回收機(jī)制。可見,JVM允許針對不同內(nèi)存區(qū)執(zhí)行不同的GC策略。
新生代GC策略 – Parallel New Collector
根據(jù)上文所述,對象初始化之后會被放入Young區(qū),更具體的話應(yīng)該是Eden區(qū),當(dāng)Eden區(qū)滿了之后,會進(jìn)行一次GC。GC算法會檢查所有對象的引用情況,如果某個對象還有被引用,表示該對象存活。檢查完成之后,會將這些存活的對象移到S0區(qū),并且回收整個Eden區(qū)空間,稱為一次Minor GC;接著新對象進(jìn)來,又會放入Eden區(qū),滿了之后會檢查S0和Eden區(qū)存活的對象,將所有存活的對象移到S1區(qū),再回收整個S0和Eden區(qū)空間;很容易理解,S0和S1兩個區(qū)總會有一個區(qū)是預(yù)留給下次存放存活對象用的。
整個過程可以使用如下圖示:
這種算法稱為復(fù)制算法,對于這種算法,有兩點需要關(guān)注:
1. 算法會執(zhí)行’stop-the-world’暫停,但時間非常短。因為Young區(qū)通常會設(shè)置的比較小(一般不建議不超過512M),而且JVM會啟動大量線程并發(fā)執(zhí)行,一次Minor GC一般都會在幾毫秒內(nèi)完成
2. 不會產(chǎn)生碎片,每次GC之后都會將存活的對象放入連續(xù)的空間(S0或S1)
內(nèi)存中所有對象都會維護(hù)一個計數(shù)器,每次Minor GC移動一個對象之后,都會為這個對象的計數(shù)器加一。當(dāng)計數(shù)器增加到一定閾值之后,算法就會認(rèn)為該對象生命周期很長,會將其移入老生代。該閾值可以通過JVM參數(shù)XX:MaxTenuringThreshold指定。
老生代GC策略 – Concurrent Mark-Sweep
每次執(zhí)行Minor GC之后,都會有部分生命周期較長的對象被移入老生代,一段時間之后,老生代空間也會被占滿。此時就需要針對老生代空間執(zhí)行GC操作,此處我們介紹Concurrent Mark-Sweep(CMS)算法。CMS算法整個流程分為6個階段,其中部分階段會執(zhí)行 ‘stop-the-world’ 暫停,部分階段會和應(yīng)用線程一起并發(fā)執(zhí)行:
1. initial-mark:這個階段虛擬機(jī)會暫停所有正在執(zhí)行的任務(wù)。這一過程虛擬機(jī)會標(biāo)記所有 ‘根對象’,所謂‘根對象’,一般是指一個運行線程直接引用到的對象。雖然會暫停整個JVM,但因為’根對象’相對較少,這個過程通常很快。
2. concurrent mark:垃圾回收器會從‘根節(jié)點’開始,將所有引用到的對象都打上標(biāo)記。這個階段應(yīng)用程序的線程和標(biāo)記線程并發(fā)執(zhí)行,因此用戶并不會感到停頓。
3. concurrent precleaning:并發(fā)預(yù)清理階段仍然是并發(fā)的。在這個階段,虛擬機(jī)查找在執(zhí)行mark階段新進(jìn)入老年代的對象(可能會有一些對象從新生代晉升到老年代, 或者有一些對象被分配到老年代)。
4. remark:在階段3的基礎(chǔ)上對查找到的對象進(jìn)行重新標(biāo)記,這一階段會暫停整個JVM,但是因為階段3已經(jīng)欲檢查出了所有新進(jìn)入的對象,因此這個過程也會很快。
5. concurrent sweep:上述3階段完成了引用對象的標(biāo)記,此階段會將所有沒有標(biāo)記的對象作為垃圾回收掉。這個階段應(yīng)用程序的線程和標(biāo)記線程并發(fā)執(zhí)行。
6. concurrent reset:重置CMS收集器的數(shù)據(jù)結(jié)構(gòu),等待下一次垃圾回收。
相應(yīng)的,對于CMS算法,也需要關(guān)注兩點:
1. ‘stop-the-world’暫停時間也很短暫,耗時較長的標(biāo)記和清理都是并發(fā)執(zhí)行的。
2. CMS算法在標(biāo)記清理之后并沒有重新壓縮分配存活對象,因此整個老生代會產(chǎn)生很多的內(nèi)存碎片。
CMS Failure Mode
上文提到在正常的情況下CMS整個流程的暫停時間都是很短的,一般也就在10ms~100ms左右。然而這與線上的情況并不相符,線上集群在讀寫壓力很大的情況下,經(jīng)常會出現(xiàn)長時間的卡頓,有些卡頓甚至長達(dá)幾分鐘,導(dǎo)致很嚴(yán)重的讀寫阻塞,甚至?xí)斐蒖egion Server和Zookeeper之間Session超時,使得Region Server異常離線。實際上,CMS并不是很完美,它會在兩種場景下產(chǎn)生嚴(yán)重的Full GC,接下來分別進(jìn)行介紹。
Concurrent Failure
這種場景其實比較簡單,假如現(xiàn)在系統(tǒng)正在執(zhí)行CMS回收老生代空間,在回收的過程中新生代來了一批對象進(jìn)來,不巧的是,老生代已經(jīng)沒有空間再容納這些對象了。這種場景下,CMS回收器會停止繼續(xù)工作,系統(tǒng)進(jìn)入 ’stop-the-world’ 模式,并且回收算法會退化為單線程復(fù)制算法,重新分配整個堆內(nèi)存的存活對象到S0中,釋放所有其他空間。很顯然,整個過程會非常’漫長’。但是這種問題也很容易解決,只需要讓CMS回收器更早一點回收就可以避免。JVM提供了參數(shù)-XX:CMSInitiatingOccupancyFraction=N來設(shè)置CMS回收的時機(jī),其中N表示當(dāng)前老生代已使用內(nèi)存占新生代總內(nèi)存的比例,該值默認(rèn)為68,可以將該值修改的更小使得回收更早進(jìn)行。
Promotion Failure
假設(shè)此時設(shè)置XX:CMSInitiatingOccupancyFraction=60,但是在已使用內(nèi)存還沒有達(dá)到總內(nèi)存60%的時候,已經(jīng)沒有空間容納從新生代遷移的對象了。oh,my god!怎么會這樣?罪魁禍?zhǔn)拙褪莾?nèi)存碎片,上文中提到CMS算法會產(chǎn)生大量碎片,當(dāng)碎片容量積累到一定大小之后就會造成上面的場景。這種場景下,CMS回收器一樣會停止工作,進(jìn)入漫長的 ’stop-the-world’ 模式。JVM也提供了參數(shù) -XX: UseCMSCompactAtFullCollection來減少碎片的產(chǎn)生,這個參數(shù)表示會在每次CMS回收垃圾之后執(zhí)行一次碎片整理,很顯然,這個參數(shù)會對性能有比較大的影響,對HBase這種對延遲敏感的業(yè)務(wù)來說并不是一個完美解決方案。
HBase內(nèi)存碎片統(tǒng)計實驗
在實際線上環(huán)境中,很少出現(xiàn)Concurrent Failure模式的Full GC,大多數(shù)Full GC場景都是Promotion Failure。我們線上集群也會每隔半個月左右就會因為Promotion Failure觸發(fā)一次Full GC。為了更好地理解CMS策略下內(nèi)存碎片是如何觸發(fā)Promotion Failure,接下來我們做一個簡單的實驗:JVM提供了參數(shù) -XX:PrintFLSStatistics=1來打印每次GC前后內(nèi)存碎片的統(tǒng)計信息,統(tǒng)計信息主要包括3個維度:Free
Space、Max Chunk Size和Num Chunks,其中Free Space表示老生代當(dāng)前空閑的總內(nèi)存容量,Max Chunk Size表示老生代中最大的內(nèi)存碎片所占的內(nèi)存容量大小,Num Chunks表示老生代中總的內(nèi)存碎片數(shù)。我們在測試環(huán)境集群(共4臺Region Server)將這個參數(shù)設(shè)置為1,然后使用一個客戶端YCSB執(zhí)行Read-And-Write操作,分別統(tǒng)計日志中Free Space和Max Chunk Size兩個指標(biāo)隨時間的變化情況。
測試結(jié)果如下圖所示,其中第一張圖表示Total Free Space隨時間的變化曲線圖,第二張圖表示Max Chunk Size隨時間變化曲線圖。其中橫坐標(biāo)表示時間,縱坐標(biāo)表示相應(yīng)內(nèi)存大小。
根據(jù)第一張曲線圖可知,老生代總的空閑內(nèi)存容量維持在300M~400M之間,當(dāng)內(nèi)存容量到達(dá)300M左右時就會進(jìn)行一次GC,GC后內(nèi)存容量就會又回到400M左右。而第二張曲線圖會更加形象地說明內(nèi)存碎片導(dǎo)致的Promotion Failure,剛開始隨著數(shù)據(jù)不斷寫入,Max Chunk Size會不斷變小,之后很長一段時間基本維持在30M左右。在橫坐標(biāo)為1093那點,人為地將寫入的單條數(shù)據(jù)大小由500Byte變?yōu)?M大小,此后Max Chunk Size會再次減小,當(dāng)減小到一定程度之后曲線會忽然升高到350M左右,經(jīng)過日志確認(rèn),此時JVM發(fā)生了Promotion
Failure模式的Full GC,持續(xù)時間約4.91s。此后一段時間Full GC還在持續(xù)發(fā)生。
經(jīng)過上述分析,可以知道:CMS GC會不斷產(chǎn)生內(nèi)存碎片,當(dāng)碎片小到一定程度之后就會基本維持不變,如果此時業(yè)務(wù)寫入一些單條數(shù)據(jù)量很大的KeyValue,就有可能觸發(fā)Promotion Failure模式Full GC。
總結(jié)
本文首先介紹了兩種常見的Java GC策略,再接著介紹了CMS策略可能引起兩種模式的Full GC,最后通過一個小實驗說明了CMS GC確實產(chǎn)生了內(nèi)存碎片,而且會導(dǎo)致長時間的Full GC發(fā)生。接下來《演進(jìn)篇》會詳細(xì)介紹從一開始HBase是如何針對CMS進(jìn)行優(yōu)化處理的,敬請期待!
Categories:更多技術(shù)交流,請關(guān)注我們進(jìn)行交流與咨詢哦!
本條技術(shù)文章來源于互聯(lián)網(wǎng),如果無意侵犯您的權(quán)益請點擊此處反饋版權(quán)投訴
本文系統(tǒng)來源:php中文網(wǎng)
總結(jié)
以上是生活随笔為你收集整理的java的前生今世_HBaseGC的前生今世-身世篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 川崎1000多少钱啊?
- 下一篇: java教学楼的属性_java设计一个父