CMS收集器FullGC的原因

GC日誌

2020-05-10T10:54:53.514+0800: 116965.198: [GC 116965.198: [ParNew (promotion failed): 1310720K->1310720K(1310720K), 9.4697020 secs]116974.668: [CMS2020-05-10T10:55:04.228+0800: 116975.911: [CMS-concurrent-mark: 5.113/24.345 secs] [Times: user=124.25 sys=9.90, real=24.34 secs] 
 (concurrent mode failure): 19185173K->5686761K(19890176K), 14.3322600 secs] 20230935K->5686761K(21200896K), [CMS Perm : 105711K->105711K(176268K)], 23.8028270 secs] [Times: user=34.33 sys=2.50, real=23.80 secs] 
Heap after GC invocations=81049 (full 25):
 par new generation   total 1310720K, used 0K [0x00000002c2000000, 0x0000000322000000, 0x0000000322000000)
  eden space 1048576K,   0% used [0x00000002c2000000, 0x00000002c2000000, 0x0000000302000000)
  from space 262144K,   0% used [0x0000000312000000, 0x0000000312000000, 0x0000000322000000)
  to   space 262144K,   0% used [0x0000000302000000, 0x0000000302000000, 0x0000000312000000)
 concurrent mark-sweep generation total 19890176K, used 5686761K [0x0000000322000000, 0x00000007e0000000, 0x00000007e0000000)
 concurrent-mark-sweep perm gen total 176268K, used 105711K [0x00000007e0000000, 0x00000007eac23000, 0x0000000800000000)
}

promotion failed

該問題是在進行Minor GC時,Survivor Space放不下,對象只能放入老年代,而此時老年代有碎片或者不能容納這些對象造成的。(promotion failed時老年代CMS還沒有機會進行回收,又放不下轉移到老年代的對象,因此會出現下一個問題concurrent mode failure)。

concurrent mode failure

該問題是在執行CMS GC的過程中同時業務線程將對象放入老年代,而此時老年代空間不足,或者在做Minor GC的時候,新生代Survivor空間放不下,需要放入老年代,而老年代也放不下而產生的。

promotion failed和concurrent mode failure的觸發區別

  • promotion failed是說,擔保機制確定老年代是否有足夠的空間容納新來的對象,如果擔保機制說有,但是真正分配的時候發現由於碎片導致找不到連續的空間而失敗;
  • concurrent mode failure是指併發週期還沒執行完,用戶線程就來請求比預留空間更大的空間了,即後臺線程的收集沒有趕上應用線程的分配速度。

CMS併發週期失敗的情況

  1. 併發模式失敗(Concurrent mode failure):CMS的目標就是在回收老年代對象的時候不要停止全部應用線程,在併發週期執行期間,用戶的線程依然在運行,如果這時候如果應用線程向老年代請求分配的空間超過預留的空間(擔保失敗),就回觸發concurrent mode failure,然後CMS的併發週期就會被一次Full GC代替——停止全部應用進行垃圾收集,並進行空間壓縮。如果我們設置了UseCMSInitiatingOccupancyOnlyCMSInitiatingOccupancyFraction參數,其中CMSInitiatingOccupancyFraction的值是70,那預留空間就是老年代的30%。
  2. 晉升失敗:新生代做minor gc的時候,需要CMS的擔保機制確認老年代是否有足夠的空間容納要晉升的對象,擔保機制發現不夠,則報concurrent mode failure,如果擔保機制判斷是夠的,但是實際上由於碎片問題導致無法分配,就會報晉升失敗。
  3. 永久代空間(或Java8的元空間)耗盡,默認情況下,CMS不會對永久代進行收集,一旦永久代空間耗盡,就回觸發Full GC。

CMS的調優

1)promtiom failed 到 concurrent mode failure 的調優

一是survior區太小,二是調低CMSInitiatingOccupancyFraction的值

2)concurrent mode failure的調優

  • 增加整個堆的大小,增大老年代的空間,或者減少年輕代的大小
  • 以更高的頻率執行後臺的回收線程,即提高CMS併發週期發生的頻率。設置UseCMSInitiatingOccupancyOnlyCMSInitiatingOccupancyFraction參數,調低CMSInitiatingOccupancyFraction的值,但是也不能調得太低,太低了會導致過多的無效的併發週期,會導致消耗CPU時間和更多的無效的停頓。
  • 增多回收線程的個數
    CMS默認的垃圾收集線程數是*(CPU個數 + 3)/4*,這個公式的含義是:當CPU個數大於4個的時候,垃圾回收後臺線程至少佔用25%的CPU資源。舉個例子:如果CPU核數是1-4個,那麼會有1個CPU用於垃圾收集,如果CPU核數是5-8個,那麼久會有2個CPU用於垃圾收集。

2)針對永久代的調優

如果永久代需要垃圾回收(或元空間擴容),就會觸發Full GC。默認情況下,CMS不會處理永久代中的垃圾,可以通過開啓CMSPermGenSweepingEnabled配置來開啓永久代中的垃圾回收,開啓後會有一組後臺線程針對永久代做收集,需要注意的是,觸發永久代進行垃圾收集的指標跟觸發老年代進行垃圾收集的指標是獨立的,老年代的閾值可以通過CMSInitiatingPermOccupancyFraction參數設置,這個參數的默認值是80%。開啓對永久代的垃圾收集只是其中的一步,還需要開啓另一個參數——CMSClassUnloadingEnabled,使得在垃圾收集的時候可以卸載不用的類。

CMS的缺點

1.浮動垃圾:由於CMS併發清理階段用戶線程還在運行着,自然會有新垃圾產生,這部分垃圾在標記過程之後,所以CMS無法在當收集中處理掉他們,只好留待下一次GC清理掉,這一部分垃圾稱爲浮動垃圾。如果CMS運行期間預留的內存無法滿足程序的需要,就會出現"Concurrent Mode Failure",然後降級臨時啓用Serial Old收集器進行老年代的垃圾收集,這樣停頓時間就很長了,所以-XX:CMSInitialOccupancyFraction設置太高容易導致大量"Concurrent Mode Failure"。

2.有空間碎片:CMS是一款基於“標記-清除”算法實現的,所以會產生空間碎片。爲了解決這個問題,CMS提供了-XX:UseCMSCompactAtFullCollection開發參數用於開啓內存碎片的合併整理,由於內存整理是無法並行的,所以停頓時間會變長。還有-XX:CMSFullGCBeforeCompaction,這個參數用於設置多少次不壓縮Full GC後,跟着來一次帶壓縮的(默認爲0)。

3.對CPU資源敏感,CMS默認啓動的回收線程數是(cpu數量+3)/4,所以CPU數量少會導致用戶程序執行速度降低較多。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章