如何下降90%Java垃圾回收時間?以阿里HBase的GC優化實踐爲例

過去的一年裏,咱們準備在Ali-HBase上突破這個被廣泛認知的痛點,爲此進行了深度分析及全面創新的工做,得到了一些比較好的效果。以螞蟻風控場景爲例,HBase的線上young GC時間從120ms減小到15ms,結合阿里巴巴JDK團隊提供的利器——AliGC,進一步在實驗室壓測環境作到了5ms。本文主要介紹咱們過去在這方面的一些工做和技術思想。node

背景算法

JVM的GC機制對開發者屏蔽了內存管理的細節,提升了開發效率。提及GC,不少人的第一反應多是JVM長時間停頓或者FGC致使進程卡死不可服務的狀況。但就HBase這樣的大數據存儲服務而言,JVM帶來的GC挑戰至關複雜和艱難。緣由有三:緩存

一、內存規模巨大。線上HBase進程多數爲96G大堆,今年新機型已經上線部分160G以上的堆配置服務器

二、對象狀態複雜。HBase服務器內部會維護大量的讀寫cache,達到數十GB的規模。HBase以表格的形式提供有序的服務數據,數據以必定的結構組織起來,這些數據結構產生了過億級別的對象和引用數據結構

三、young GC頻率高。訪問壓力越大,young區的內存消耗越快,部分繁忙的集羣能夠達到每秒1~2次youngGC, 大的young區能夠減小GC頻率,可是會帶來更大的young GC停頓,損害業務的實時性需求。併發

思路高併發

1.HBase做爲一個存儲系統,使用了大量的內存做爲寫buffer和讀cache,好比96G的大堆(4G young + 92G old)下,寫buffer+讀cache會佔用70%以上的內存(約70G),自己堆內的內存水位會控制在85%,而剩餘的佔用內存就只有在10G之內了。因此,若是咱們能在應用層面自管理好這70G+的內存,那麼對於JVM而言,百G大堆的GC壓力就會等價於10G小堆的GC壓力,而且將來面對更大的堆也不會惡化膨脹。 在這個解決思路下,咱們線上的young GC時間得到了從120ms到15ms的優化效果。性能

2.在一個高吞吐的數據密集型服務系統中,大量的臨時對象被頻繁建立與回收,如何可以針對性管理這些臨時對象的分配與回收,AliJDK團隊研發了一種新的基於租戶的GC算法—AliGC。集團HBase基於這個新的AliGC算法進行改造,咱們在實驗室中壓測的young GC時間從15ms減小到5ms,這是一個不曾指望的極致效果。大數據

下面將逐一介紹Ali-HBase版本GC優化所使用的關鍵技術。優化

消滅一億個對象:更快更省的CCSMap

目前HBase使用的存儲模型是LSMTree模型,寫入的數據會在內存中暫存到必定規模後再dump到磁盤上造成文件。

下面咱們將其簡稱爲寫緩存。寫緩存是可查詢的,這就要求數據在內存中有序。爲了提升併發讀寫效率,並達成數據有序且支持seek&scan的基本要求,SkipList是使用得比較普遍的數據結構。

咱們以JDK自帶的ConcurrentSkipListMap爲例子進行分析,它有下面三個問題:

1.內部對象繁多。每存儲一個元素,平均須要4個對象(index+node+key+value,平均層高爲1)

2.新插入的對象在young區,老對象在old區。當不斷插入元素時,內部的引用關係會頻繁發生變化,不管是ParNew算法的CardTable標記,仍是G1算法的RSet標記,都有可能觸發old區掃描。

3.業務寫入的KeyValue元素並非規整長度的,當它晉升到old區時,可能產生大量的內存碎片。

問題1使得young區GC的對象掃描成本很高,young GC時晉升對象更多。問題2使得young GC時須要掃描的old區域會擴大。問題3使得內存碎片化致使的FGC機率升高。當寫入的元素較小時,問題會變得更加嚴重。咱們曾對線上的RegionServer進程進行統計,活躍Objects有1億2千萬之多!

分析完當前young GC的最大敵人後,一個大膽的想法就產生了,既然寫緩存的分配,訪問,銷燬,回收都是由咱們來管理的,若是讓JVM「看不到」寫緩存,咱們本身來管理寫緩存的生命週期,GC問題天然也就迎刃而解了。

提及讓JVM「看不到」,可能不少人想到的是off-heap的解決方案,可是這對寫緩存來講沒那麼簡單,由於即便把KeyValue放到offheap,也沒法避免問題1和問題2。而1和2也是young GC的最大困擾。

問題如今被轉化成了:如何不使用JVM對象來構建一個有序的支持併發訪問的Map。

固然咱們也不能接受性能損失,由於寫入Map的速度和HBase的寫吞吐息息相關。

需求再次強化:如何不使用對象來構建一個有序的支持併發訪問的Map,且不能有性能損失。

閱讀全文

相關文章
相關標籤/搜索