天天看點

【JVM性能優化】 性能調優之CMS垃圾回收器(上)前提概要CMS垃圾回收的6個重要階段分析優化目标JVM參數調優總結

前提概要

如果沒有冬天,春天不會如此悅人;如果沒有偶爾的不幸,幸運不會如此受人歡迎。

CMS垃圾回收的6個重要階段

  1. initial-mark 初始标記(CMS的第一個STW階段),标記GC Root直接引用的對象,GC Root直接引用的對象不多,是以很快。
  2. concurrent-mark并發标記階段,由第一階段标記過的對象出發,所有可達的對象都在本階段标記。
  3. concurrent-preclean 并發預清理階段,也是一個并發執行的階段。在本階段,會查找前一階段執行過程中,[從新生代晉升或新配置設定或被更新的對象]。通過并發地重新掃描這些對象,預清理階段可以減少下一個stop-the-world 重新标記階段的工作量。
  4. concurrent-abortable-preclean,并發可中止的預清理階段。這個階段其實跟上一個階段做的東西一樣,也是為了減少下一個STW重新标記階段的工作量。增加這一階段是為了讓我們可以控制這個階段的結束時機,比如掃描多長時間(預設5秒)或者Eden區使用占比達到期望比例(預設50%)就結束本階段。
  5. remark重标記階段(CMS的第二個STW階段),暫停所有使用者線程,從GC Root開始重新掃描整堆,标記存活的對象。需要注意的是,雖然CMS隻回收老年代的垃圾對象,但是這個階段依然需要掃描新生代,因為很多GC Root都在新生代,而這些GC Root指向的對象又在老年代,這稱為“跨代引用”。
  6. concurrent-sweep ,并發清理。

分析

分析其GC日志,發現GC發生在CMS的收集階段。

【JVM性能優化】 性能調優之CMS垃圾回收器(上)前提概要CMS垃圾回收的6個重要階段分析優化目标JVM參數調優總結
  • 箭頭1 顯示abortable-preclean階段耗時4.04秒。
  • 箭頭2 顯示的是remark階段,耗時0.11秒。
  • 雖然abortable-preclean階段是concurrent的,不會暫停其他的使用者線程。就算不優化,可能影響也不大。

調優之前先看下該應用的GC統計資料,包括GC次數,耗時:

【JVM性能優化】 性能調優之CMS垃圾回收器(上)前提概要CMS垃圾回收的6個重要階段分析優化目标JVM參數調優總結
統計期間内(18天)發生CMS GC 69次,其中abortable preclean階段平均耗時2.45秒,final remark階段平均112ms,最大耗時170ms。

優化目标

降低abortable preclean時間,而且不增加final remark的時間(因為remark是STW的)。

JVM參數調優

第一次調優

先嘗試調低

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           

這兩個設定一般配合使用,一般用于『降低CMS GC頻率或者增加頻率、減少GC時長』的需求

  • -XX:CMSInitiatingOccupancyFraction=80 是指設定CMS在對記憶體占用率達到80%的時候開始GC(因為CMS會有浮動垃圾,是以一般都較早啟動GC);
  • -XX:+UseCMSInitiatingOccupancyOnly :标志來指令JVM不基于運作時收集的資料來啟動CMS垃圾收集周期。

當該标志被開啟時,JVM通過CMSInitiatingOccupancyFraction的值進行每一次CMS收集,而不僅僅是第一次。(否則後續會動态控制回收門檻值)

(慎用) 是以,隻有當我們充足的理由(比如測試)并且對應用程式産生的對象的生命周期有深刻的認知時,才應該使用該标志。

老年代的增長是由于部分對象在

Minor GC

後仍然存活,被晉升到老年代,導緻老年代使用占比增長的,也就是在每次

CMS GC

發生之前剛剛發生過一次

Minor GC

,是以在那一刻新生代的使用占比是很低的。

那麼我們預計這個時候盡快結束abortable preclean階段,在remark時就不需要掃描太多的Eden區對象,remark STW的時間也就不會太長。

第一次調整參數

【JVM性能優化】 性能調優之CMS垃圾回收器(上)前提概要CMS垃圾回收的6個重要階段分析優化目标JVM參數調優總結
在統計期間(17小時左右)内,發生過2次CMS GC。Abortable Preclean 平均耗時835ms,這是預期内的。但是Final Remark 平均耗時495ms(調整前是112ms),其中一次是80ms,另一次是910ms!将近1秒鐘!Remark是STW的!對于要求低延時的應用來說這是無法接受的!
【JVM性能優化】 性能調優之CMS垃圾回收器(上)前提概要CMS垃圾回收的6個重要階段分析優化目标JVM參數調優總結

[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耗時會出現過程gc;縮短的話,remark階段又會變長,而且是STW,更不能接受。
對于這種情況,CMS提供了CMSScavengeBeforeRemark參數,嘗試在remark階段之前進行一次Minor GC,以降低新生代的占用。

第二次調優

增加 -XX:+CMSScavengeBeforeRemark 不是沒有代價的,因為這會增加一次Minor GC停頓。是以這個方案好或者不好的判斷标準就是:增加CMSScavengeBeforeRemark參數之後的minor GC停頓時間 + remark 停頓時間如果比增加之前的remark GC停頓時間要小,這才是好的方案。

-XX:+CMSScavengeBeforeRemark: 在CMS GC前啟動一次ygc,目的在于減少old gen對ygc gen的引用,降低remark時的開銷-----一般CMS的GC耗時 80%都在remark階段

第二次調整的結果

在統計期間(20小時左右)内,發生3次CMS GC。Abortable preclean 平均耗時693ms。Final remark平均耗時50ms,最大耗時60ms。Final remark的時間比調優前的平均時間(112ms)更低。
【JVM性能優化】 性能調優之CMS垃圾回收器(上)前提概要CMS垃圾回收的6個重要階段分析優化目标JVM參數調優總結

3次CMS GC remark前的Minor GC日志分析

第1次是非高峰時段的表現,Minor GC 耗時 0.01s + remark耗時 0.06s = 0.07s = 70ms,如下

【JVM性能優化】 性能調優之CMS垃圾回收器(上)前提概要CMS垃圾回收的6個重要階段分析優化目标JVM參數調優總結

第2次是高峰時段,Minor GC 耗時 0.01s + remark耗時 0.05s = 0.06s = 60ms,如下

【JVM性能優化】 性能調優之CMS垃圾回收器(上)前提概要CMS垃圾回收的6個重要階段分析優化目标JVM參數調優總結

第3次是非高峰時段,Minor GC 耗時 0.00s + remark耗時 0.04s = 0.04s = 40ms,如下

【JVM性能優化】 性能調優之CMS垃圾回收器(上)前提概要CMS垃圾回收的6個重要階段分析優化目标JVM參數調優總結
是以,3次Minor GC + remark耗時的平均耗時 < 60ms,這比第一次調優時remark平均耗時495ms好得多了。

總結

解決abortable preclean 時間過長的方案可以歸結為兩步:

縮短abortable preclean 時長,通過調整這兩個參數:

-XX:CMSMaxAbortablePrecleanTime=xxx

-XX:CMSScheduleRemarkEdenPenetration=xxx           
  • 而如果新生代增長過快,像這次調優應用2秒内就能用光2G新生代堆空間的,就隻能通過CMSScavengeBeforeRemark做一次Minor GC了。