1. 背景
多個業務線的應用出現LongGC告警前端
最近一段時間,常常收到CAT報出來的Long GC告警(配置爲大於3秒的爲Longgc)。java
2. 知識回顧
2.1 JVM堆內存劃分
-
新生代(Young Generation)
新生代內被劃分爲三個區:Eden,from survivor,to survivor。大多數對象在新生代被建立。Minor GC針對的是新生代的垃圾回收。python
-
老年代(Old Generation)
在新生代中經歷了幾回Minor GC仍然存活的對象,就會被放到老年代。Major GC針對的是老年代的垃圾回收。本文重點分析的CMS就是一種針對老年代的垃圾回收算法。另外Full GC是針對整堆(包括新生代和老年代)作垃圾回收的。web
-
永久代(Perm)
主要存放已被虛擬機加載的類信息,常量,靜態變量等數據。該區域對垃圾回收的影響不大,本文不會過多涉及。面試
2.2 CMS垃圾回收的6個重要階段
一、initial-mark 初始標記(CMS的第一個STW階段),標記GC Root直接引用的對象,GC Root直接引用的對象很少,因此很快。算法
二、concurrent-mark 併發標記階段,由第一階段標記過的對象出發,全部可達的對象都在本階段標記。小程序
三、concurrent-preclean 併發預清理階段,也是一個併發執行的階段。在本階段,會查找前一階段執行過程當中,重新生代晉升或新分配或被更新的對象。經過併發地從新掃描這些對象,預清理階段能夠減小下一個stop-the-world 從新標記階段的工做量。緩存
四、concurrent-abortable-preclean 併發可停止的預清理階段。這個階段其實跟上一個階段作的東西同樣,也是爲了減小下一個STW從新標記階段的工做量。增長這一階段是爲了讓咱們能夠控制這個階段的結束時機,好比掃描多長時間(默認5秒)或者Eden區使用佔比達到指望比例(默認50%)就結束本階段。微信
五、remark 重標記階段(CMS的第二個STW階段),暫停全部用戶線程,從GC Root開始從新掃描整堆,標記存活的對象。須要注意的是,雖然CMS只回收老年代的垃圾對象,可是這個階段依然須要掃描新生代,由於不少GC Root都在新生代,而這些GC Root指向的對象又在老年代,這稱爲「跨代引用」。架構
六、concurrent-sweep ,併發清理。
3. 分析
下面先看看出現LongGC時發生了什麼。
選取其中一個應用分析其GC日誌,發現LongGC發生在CMS 的收集階段。
箭頭1 顯示abortable-preclean階段耗時4.04秒。箭頭2 顯示的是remark階段,耗時0.11秒。
雖然abortable-preclean階段是concurrent的,不會暫停其餘的用戶線程。就算不優化,可能影響也不大。可是每天收到各個業務線的gc報警,長久來講也不是好事。
在調優以前先看下該應用的GC統計數據,包括GC次數,耗時:
統計期間內(18天)發生CMS GC 69次,其中 abortable preclean階段平均耗時2.45秒,final remark階段平均112ms,最大耗時170ms.
4. 優化目標
下降abortable preclean 時間,並且不增長final remark的時間(由於remark是STW的)。
5. JVM參數調優
5.1 第一次調優
先嚐試調低abortable preclean階段的時間,看看效果。
有兩個參數能夠控制這個階段什麼時候結束:
-
-XX:CMSMaxAbortablePrecleanTime=5000
默認值5s,表明該階段最大的持續時間
-
-XX:CMSScheduleRemarkEdenPenetration=50
默認值50%,表明Eden區使用比例超過50%就結束該階段進入remark
調整爲最大持續時間爲1s,Eden區使用佔比10%,以下:
-XX:CMSMaxAbortablePrecleanTime=1000
-XX:CMSScheduleRemarkEdenPenetration=10
爲何調整成這樣兩個值,咱們是這樣考慮的:首先每次CMS都發生在老年代使用佔比達到80%時,由於這是由下面兩個參數決定的:
-XX:CMSInitiatingOccupancyFraction=80
-XX:+UseCMSInitiatingOccupancyOnly
而老年代的增加是因爲部分對象在Minor GC後仍然存活,被晉升到老年代,致使老年代使用佔比增加的,也就是在每次CMS GC發生以前剛剛發生過一次Minor GC,因此在那一刻新生代的使用佔比是很低的。那麼咱們預計這個時候儘快結束abortable preclean階段,在remark時就不須要掃描太多的Eden區對象,remark STW的時間也就不會太長。
調整的思路是這樣了,那到底效果如何呢?
第一次調整的的結果
在統計期間(17小時左右)內,發生過2次CMS GC。Abortable Preclean 平均耗時835ms,這是預期內的。可是Final Remark 平均耗時495ms(調整前是112ms),其中一次是80ms,另外一次是910ms!將近1秒鐘!Remark是STW的!對於要求低延時的應用來講這是沒法接受的!
對比這兩次CMS GC的詳細GC日誌,咱們發現了一些對分析問題很是有用的東西。
remark耗時80ms的那次GC日誌
[YG occupancy: 181274 K (1887488 K)]
- 年輕代當前佔用狀況和總容量
耗時80ms的此次remark發生時(早上9點,非高峯時段),新生代(YG)佔用181.274M。
remark耗時910ms的那次GC日誌
[YG occupancy: 773427 K (1887488 K)]
耗時910ms的此次remark發生時(晚上10點左右,高峯時段),新生代(YG)佔用773.427M。由於這個時候高峯期,新生代的佔用量上升的很是快,幾乎一樣的時間內,非高峯時段僅上升到181M,可是高峯時段就上升到773M。
這裏能得出一個有用的結論:若是abortale preclean階段時間過短,隨後在remark時,新生代佔用越大,則remark持續的時間(STW)越長。
這就陷入了兩難了,不縮短abortale preclean耗時會報longgc;縮短的話,remark階段又會變長,並且是STW,更不能接受。
對於這種狀況,CMS提供了CMSScavengeBeforeRemark參數,嘗試在remark階段以前進行一次Minor GC,以下降新生代的佔用。
-XX:+CMSScavengeBeforeRemark
Enables scavenging attempts before the CMS remark step. By default, this option is disabled.
5.2 第二次調優
調優前的考慮:
增長-XX:+CMSScavengeBeforeRemark
不是沒有代價的,由於這會增長一次Minor GC停頓。因此這個方案好或者很差的判斷標準就是:增長CMSScavengeBeforeRemark參數以後的minor GC停頓時間 + remark 停頓時間若是比增長以前的remark GC停頓時間要小,這纔是好的方案。
第二次調整的結果
在統計期間(20小時左右)內,發生3次CMS GC。Abortable preclean 平均耗時693ms。Final remark平均耗時50ms,最大耗時60ms。Final remark的時間比調優前的平均時間(112ms)更低。
那麼CMS GC前的Minor GC停頓時間又如何呢?來看看詳細的GC日誌。
3次CMS GC remark前的Minor GC日誌分析
第1次是非高峯時段的表現,Minor GC 耗時 0.01s + remark耗時 0.06s = 0.07s = 70ms,以下
第2次是高峯時段,Minor GC 耗時 0.01s + remark耗時 0.05s = 0.06s = 60ms,以下
第3次是非高峯時段,Minor GC 耗時 0.00s + remark耗時 0.04s = 0.04s = 40ms,以下
因此,3次Minor GC + remark耗時的平均耗時 < 60ms,這比第一次調優時remark平均耗時495ms好得多了。
6.優化結果
至此,咱們最初的目標- 下降abortable preclean 時間,並且不增長final remark的時間 ,已經達到了。甚至remark的時間也縮短了。
7. 小結
解決abortable preclean 時間過長的方案能夠歸結爲兩步:
-
縮短abortable preclean 時長,經過調整這兩個參數:
-XX:CMSMaxAbortablePrecleanTime=xxx
-XX:CMSScheduleRemarkEdenPenetration=xxx
調整爲多少的一個判斷標準是:abortable preclean階段結束時,新生代的空間佔用不能大於某個參考值。 在前面第一次調優後,新生代(YG)佔用181.274M,remark耗時80ms;新生代(YG)佔用773.427M時,remark耗時910ms。因此這個參考值能夠是300M。而若是新生代增加過快,像此次調優應用2秒內就能用光2G新生代堆空間的,就只能經過CMSScavengeBeforeRemark作一次Minor GC了。
-
增長CMSScavengeBeforeRemark參數開啓remark前進行Minor GC的嘗試
雖然官方說明這個增長這個參數是嘗試進行Minor GC,不必定會進行。但實際使用起來,幾乎每次remark前都會Minor GC。
8. 總結
-
調優前明確目標 -
調優過程對GC指標進行數據統計分析(本文藉助gceasy.io在線分析工具)來驗證效果 -
須要能看懂GC日誌 -
GC調優不是一個一蹴而就的事情,它是微調-觀察-再微調的過程。因此須要比較深刻了解GC的一些基礎,才能少走彎路。
在公衆號菜單中可自行獲取專屬架構視頻資料,包括不限於 java架構、python系列、人工智能系列、架構系列,以及最新面試、小程序、大前端均無私奉獻,你會感謝個人哈
往期熱門文章:
1,架構的本質:如何打造一個有序的系統?
2,
分佈式高可靠之負載均衡,今天看了你確定會
3,
分佈式數據之緩存技術,一塊兒來揭開其神祕面紗
4,分佈式數據複製技術,今天就教你真正分身術
5,
數據分佈方式之哈希與一致性哈希,我就是個神算子
6
,分佈式存儲系統三要素,掌握這些就離成功不遠了
7
,想要設計一個好的分佈式系統,必須搞定這個理論
8
,
分佈式通訊技術之發佈訂閱,乾貨滿滿
9,
分佈式通訊技術之遠程調用:RPC
10
,秒殺系統每秒上萬次下單請求,咱們該怎麼去設計
本文分享自微信公衆號 - 架構師修煉(jiagouxiulian)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。