JVM GC耗時頻頻升高,此次排查完想說:還有誰?

點擊上方藍色字關注咱們~








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. 總結

  1. 調優前明確目標
  2. 調優過程對GC指標進行數據統計分析(本文藉助gceasy.io在線分析工具)來驗證效果
  3. 須要能看懂GC日誌
  4. GC調優不是一個一蹴而就的事情,它是微調-觀察-再微調的過程。因此須要比較深刻了解GC的一些基礎,才能少走彎路。
來源 | https://urlify.cn/mM7rIr


在公衆號菜單中可自行獲取專屬架構視頻資料,包括不限於 java架構、python系列、人工智能系列、架構系列,以及最新面試、小程序、大前端均無私奉獻,你會感謝個人哈


往期熱門文章:

1,架構的本質:如何打造一個有序的系統?
2, 分佈式高可靠之負載均衡,今天看了你確定會
3, 分佈式數據之緩存技術,一塊兒來揭開其神祕面紗
4,分佈式數據複製技術,今天就教你真正分身術
5, 數據分佈方式之哈希與一致性哈希,我就是個神算子
分佈式存儲系統三要素,掌握這些就離成功不遠了
想要設計一個好的分佈式系統,必須搞定這個理論
分佈式通訊技術之發佈訂閱,乾貨滿滿
9, 分佈式通訊技術之遠程調用:RPC
10  秒殺系統每秒上萬次下單請求,咱們該怎麼去設計



本文分享自微信公衆號 - 架構師修煉(jiagouxiulian)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索