如何降低 young gc 時間
基礎知識
young gc 主要採用的是copying GC演算法;copying GC演算法主要有以下兩個步驟:
-
Root Scanning
-
Object Copy
copying Gc的執行過程大概是從 Gc roots開始掃描其引用,掃描到的就是認為是存活的物件,其他的就是不需要的物件,然後把存放物件進行移動就OK了。
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中 跟業務方最相關的就是j ava執行緒 ,那要是把執行緒數減少是不是能降低 Root Scanning,進而降低整個young gc 時間呢。
筆者負責的專案大部分專案都是採用Hystrix執行緒池作為超時熔斷降級,因為依賴的下游介面很多很多並且很多時候需要分批,導致執行緒數特別多,高達4000+,young gc 時root scanning 佔用了 15ms左右,young gc 日誌如下:
我採用了 Hystrix訊號量+RPC非同步化去改造專案,減少執行緒池數目。改造之後執行緒數在700左右,young gc時 root scanning 佔用的的時間 < 5ms。
降低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 日誌如下:
筆者把dx17的young 區域調大 (調整之後 Eden為 1677824K),dx14的 Eden為 921600K,調整前後的gc 時間如下:
從上面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請求沒啥影響,但是自己寫測試確實有比較大的影響,沒有明白為什麼。
使用物件池,減少物件產生。