如何降低young gc時間

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"基礎知識"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"young gc 主要採用的是copying GC算法;copying GC算法主要有以下兩個步驟:"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Root Scanning"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Object Copy"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"copying Gc的執行過程大概是從 Gc roots開始掃描其引用,掃描到的就是認爲是存活的對象,其他的就是不需要的對象,然後把存放對象進行移動就OK了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cd/cdec300a7d2bc394fab0bd3f57e1a157.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"young gc 的耗時也基本上都在這兩個步驟上。要想減少一次young gc的時間,必須想辦法減少上面兩步耗時。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據官方文檔可以知道,GC roots 包含以下引用:"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"所有java線程以及線程棧幀裏指向GC堆裏的對象的引用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"JNI Local & Global"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"由系統類加載器(system class loader)加載的對象,這些類是不能夠被回收的"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"stack local Java方法的local變量或參數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"其他,包含monitor & finalizable & native stack 等吧"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Copying GC算法最主要的特徵就是它的gc 時間只跟活對象的多少有關係,而跟它所管理的堆空間的大小沒關係"},{"type":"text","text":"。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"如何降低每次young gc 的時間呢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面的分析可以知道只要減少GC roots集合大小以及降低每次gc 之後的存活對象就可以了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在GC roots中 跟業務方最相關的就是java線程,那要是把線程數減少是不是能降低 Root Scanning,進而降低整個young gc 時間呢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"筆者負責的項目大部分項目都是採用Hystrix線程池作爲超時熔斷降級,因爲依賴的下游接口很多很多並且很多時候需要分批,導致線程數特別多,高達4000+,young gc 時root scanning 佔用了 15ms左右,young gc 日誌如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a8/a81d29a52a6c5e24ef9bb78e0f37707e.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我採用了Hystrix信號量+RPC異步化去改造項目,減少線程池數目。改造之後線程數在700左右,young gc時root scanning 佔用的的時間 < 5ms。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/72/72509bce6dfc846b14a8f54b0b7c5c21.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"降低young gc的總時間"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"調整Eden區域大小對應用產生的可能影響分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相同的應用一般來說 gc roots 應該是保持不變的,可以簡單認爲Root Scanning相等(其實live object會影響到掃描時間,但是影響和object copy相比很小)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來看看Object Copy可能受到的影響(假設Survivor區域足夠大,不會因爲copy過程中Survivor不夠大直接晉升到old區域)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先看第一部分,Eden移動到Survivor情況"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設機器2 Eden區域是 機器1 的兩倍大,其他條件都保持不變;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就一般情況來說(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對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結 機器2由Eden區域移動到Survivor的量就是機器1 age1 + age2的量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二部分的分析邏輯和第一部分的差不多,邏輯自己推。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"結論:如果age1 遠大於 age2中的值,那麼調大Eden區域對減少young gc 次數會很明顯,並且每次young gc time時間變化不大,能明顯降低young gc總體時間"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了驗證上面的理論分析,筆者找了一個young gc 之後age1>>>age2的項目,young gc 日誌如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/58/584a1a890b75fc0a3080ce3ea9a9353e.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"筆者把dx17的young 區域調大 (調整之後Eden爲1677824K),dx14的Eden爲921600K,調整前後的gc 時間如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0d/0d301c773532f4b0163f181fc52ef669.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9b/9b8e44457ce958e777e81199c0da1381.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ab/abb35b7db34c07c945a04a797efdc850.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面3張圖可以看出 整體每分鐘 young gc 時間由 125ms —>70ms,young gc 次數由 每分鐘12.7 —> 7,每次young gc的時間仍舊是9.4左右。用awk做了一下統計發現每次young gc 之後的live object的大小由2.85M增加到了3.3M。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"減少對象生成 以到達降低young gc 次數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"儘量使用小對象,並且在方法內和線程內分配對象,利用JIT在優化時對象在棧上分配,減少在堆上分配內存,可以參考淺談HotSpot逃逸分析,但是筆者在關閉逃逸分析的時候(-XX:-DoEscapeAnalysis),對線上機器,對GC請求沒啥影響,但是自己寫測試確實有比較大的影響,沒有明白爲什麼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用對象池,減少對象產生。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"看完三件事❤️"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":""}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"點贊,轉發,有你們的 『點贊和評論』,纔是我創造的動力。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"關注公衆號 『 "},{"type":"text","marks":[{"type":"strong"}],"text":"java爛豬皮"},{"type":"text","text":" 』,不定期分享原創知識。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"同時可以期待後續文章ing🚀"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/34/34172ad7f3cc8e0f28bd1fc6ca2d2b68.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:朱紀兵"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"出處:https://club.perfma.com/article/604033"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章