Categories
程式開發

如何降低young gc時間


基礎知識

young gc 主要採用的是copying GC算法;copying GC算法主要有以下兩個步驟:

根ScanningObject複製

copying Gc的執行過程大概是從Gc roots開始掃描其引用,掃描到的就是認為是存活的對象,其他的就是不需要的對象,然後把存放對象進行移動就OK了。

如何降低young gc時間 1

young gc 的耗時也基本上都在這兩個步驟上。要想減少一次young gc的時間,必須想辦法減少上面兩步耗時。

根據官方文檔可以知道,GC roots 包含以下引用:

所有java線程以及線程棧幀裡指向GC堆裡的對象的引用JNI Local & Global由系統類加載器(system class loader)加載的對象,這些類是不能夠被回收的stack local Java方法的local變量或參數其他,包含monitor & finalizable & native stack 等吧

Copying GC算法最主要的特徵就是它的gc 時間只跟活對象的多少有關係,而跟它所管理的堆空間的大小沒關係。

如何降低每次young gc 的時間呢

從上面的分析可以知道只要減少GC roots集合大小以及降低每次gc 之後的存活對象就可以了。

在GC roots中跟業務方最相關的就是java線程,那要是把線程數減少是不是能降低Root Scanning,進而降低整個young gc 時間呢。

筆者負責的項目大部分項目都是採用Hystrix線程池作為超時熔斷降級,因為依賴的下游接口很多很多並且很多時候需要分批,導致線程數特別多,高達4000+,young gc 時root scanning 佔用了15ms左右,young gc 日誌如下:

如何降低young gc時間 2

我採用了Hystrix信號量+RPC異步化去改造項目,減少線程池數目。改造之後線程數在700左右,young gc時root scanning 佔用的的時間< 5ms。

如何降低young gc時間 3

降低young gc的總時間

調整Eden區域大小對應用產生的可能影響分析

相同的應用一般來說gc roots 應該是保持不變的,可以簡單認為Root Scanning相等(其實live object會影響到掃描時間,但是影響和object copy相比很小)

我們來看看Object Copy可能受到的影響(假設Survivor區域足夠大,不會因為copy過程中Survivor不夠大直接晉升到old區域)。

先看第一部分,Eden移動到Survivor情況

假設機器2 Eden區域是機器1 的兩倍大,其他條件都保持不變;

就一般情況來說(Survivor區域中存活的對像比Eden少很多,比如1%),那麼機器1 young gc的頻率是機器2 young gc 頻率的2倍;那麼假設機器1在T時間內GC一次,在GC之後由Eden區域晉升到Survivor的大小為10M(即age=1),那麼機器2在2T時間之後發生GC,1T-2T之間生成的對象和機器1類似,GC之後有10M進入Survivor區域,但是0T-1T內最多會剩餘10M內存可能會進入到Survivor,但是在經歷1T-2T時間之後也有可能導致object已經不存活,如何判斷這部分對像有沒有存活呢,在機器1在2T的時間點要又要進行一次young gc,那麼在0T-1T之前存活的對像也就是age=1的對象將會再次會經歷一次young gc,便是了age=2,所有看age=2的年齡段剩餘多少就可以了。機器2一次GC之後,由Eden區域進入到Survivor區域中的大約等於10M+機器1中Survivor中(age=2)也就是機器1:age1+age2中的object對象。

總結機器2由Eden區域移動到Survivor的量就是機器1 age1 + age2的量。

第二部分的分析邏輯和第一部分的差不多,邏輯自己推。

結論:如果age1 遠大於age2中的值,那麼調大Eden區域對減少young gc 次數會很明顯,並且每次young gc time時間變化不大,能明顯降低young gc總體時間。

為了驗證上面的理論分析,筆者找了一個young gc 之後age1>>>age2的項目,young gc 日誌如下:

如何降低young gc時間 4

筆者把dx17的young 區域調大(調整之後Eden為1677824K),dx14的Eden為921600K,調整前後的gc 時間如下:

如何降低young gc時間 5

如何降低young gc時間 6

如何降低young gc時間 7

從上面3張圖可以看出整體每分鐘young gc 時間由125ms —>70ms,young gc 次數由每分鐘12.7 —> 7,每次young gc的時間仍舊是9.4左右。用awk做了一下統計發現每次young gc 之後的live object的大小由2.85M增加到了3.3M。

減少對像生成以到達降低young gc 次數

盡量使用小對象,並且在方法內和線程內分配對象,利用JIT在優化時對像在棧上分配,減少在堆上分配內存,可以參考淺談HotSpot逃逸分析,但是筆者在關閉逃逸分析的時候( -XX:-DoEscapeAnalysis),對線上機器,對GC請求沒啥影響,但是自己寫測試確實有比較大的影響,沒有明白為什麼。

使用對像池,減少對象產生。

看完三件事❤️

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

點贊,轉發,有你們的『點贊和評論』,才是我創造的動力。關注公眾號『 java爛豬皮 』,不定期分享原創知識。同時可以期待後續文章ing🚀

如何降低young gc時間 8

作者:朱紀兵出處:https://club.perfma.com/article/604033