使用CMS垃圾收集器产生的问题和解决方案
??點擊上方?好好學java?,選擇?星標?公眾號
重磅資訊、干貨,第一時間送達 今日推薦:2020,搞個 Mac 玩玩!個人原創+1博客:點擊前往,查看更多 作者:藤倫柳揶 鏈接:https://www.jianshu.com/p/dbd32622ad201.問題預覽
CMS并行GC是大多數應用的最佳選擇,然而, CMS并不是完美的,在使用CMS的過程中會產生2個最讓人頭痛的問題:
promotion failed
concurrent mode failure
第一個問題promotion failed是在進行Minor GC時,Survivor Space放不下,對象只能放入老年代,而此時老年代也放不下造成的,多數是由于老年帶有足夠的空閑空間,但是由于碎片較多,這時如果新生代要轉移到老年帶的對象比較大,所以,必須盡可能提早觸發老年代的CMS回收來避免這個問題(promotion failed時老年代CMS還沒有機會進行回收,又放不下轉移到老年帶的對象,因此會出現下一個問題concurrent mode failure,需要stop-the-wold GC- Serail Old)。
下面是一個promotion failed的一條gc日志:
106.641: [GC 106.641: [ParNew (promotion failed): 14784K->14784K(14784K), 0.0370328 secs]106.678: [CMS106.715: [CMS-concurrent-mark: 0.065/0.103 secs] [Times: user=0.17 sys=0.00, real=0.11 secs] (concurrent mode failure): 41568K->27787K(49152K), 0.2128504 secs] 52402K->27787K(63936K), [CMS Perm : 2086K->2086K(12288K)], 0.2499776 secs] [Times: user=0.28 sys=0.00, real=0.25 secs]第二個問題concurrent mode failure是在執行CMS GC的過程中同時業務線程將對象放入老年代,而此時老年代空間不足,這時CMS還沒有機會回收老年帶產生的,或者在做Minor GC的時候,新生代救助空間放不下,需要放入老年代,而老年代也放不下而產生的。
盡管CMS使用一個叫做分配擔保的機制,每次Minor GC之后要保證新生代的空間survivor + eden > 老年帶的空閑時間,但是對象分配是不可預測的,總會有寫對象分配在老年帶是滿足不了的。
下面是一個concurrent mode failure的一條gc日志:
0.195: [GC 0.195: [ParNew: 2986K->2986K(8128K), 0.0000083 secs]0.195: [CMS0.212: [CMS-concurrent-preclean: 0.011/0.031 secs] [Times: user=0.03 sys=0.02, real=0.03 secs] (concurrent mode failure): 56046K->138K(57344K), 0.0271519 secs] 59032K->138K(65472K), [CMS Perm : 2079K->2078K(12288K)], 0.0273119 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]2.原因和解決方案
首先我們經常遇到promotion failed問題,這也確實是個很頭痛的問題,一般是進行Minor GC的時候,發現救助空間不夠,所以,需要移動一些新生帶的對象到老年帶,然而,有些時候盡管老年帶有足夠的空間,但是由于CMS采用標記清除算法,默認并不使用標記整理算法,可能會產生很多碎片,因此,這些碎片無法完成大對象向老年帶轉移,因此需要進行CMS在老年帶的Full GC來合并碎片。
這個問題的直接影響就是它會導致提前進行CMS Full GC, 盡管這個時候CMS的老年帶并沒有填滿,只不過有過多的碎片而已,但是Full GC導致的stop-the-wold是難以接受的。
解決這個問題的辦法就是可以讓CMS在進行一定次數的Full GC(標記清除)的時候進行一次標記整理算法,CMS提供了以下參數來控制:
-XX:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5也就是CMS在進行5次Full GC(標記清除)之后進行一次標記整理算法,從而可以控制老年帶的碎片在一定的數量以內,甚至可以配置CMS在每次Full GC的時候都進行內存的整理。
另外,有些應用存在比較大的對象朝生熄滅,這些對象在救助空間無法容納,因此,會提早進入老年帶,老年帶如果有碎片,也會產生promotion failed, 因此我們應該控制這樣的對象在新生代,然后在下次Minor GC的時候就被回收掉,這樣避免了過早的進行CMS Full GC操作,下面的一個配置樣例就通過增加救助空間的大小來解決這個問題:
-Xmx4000M -Xms4000M -Xmn600M -XXmSize=500M -XX:MaxPermSize=500M -Xss256K -XX:+DisableExplicitGC -XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled eCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log上面討論了promotion failed引起的原因以及解決方案,除了promotion failed還有一個情況會引起CMS回收失敗,從而退回到Serial Old收集器進行回收,我們在線上尤其要注意的是concurrent mode failure出現的頻率,這可以通過-XX:+PrintGCDetails來觀察,當出現concurrent mode failure的現象時,就意味著此時JVM將繼續采用Stop-The-World的方式來進行Full GC,這種情況下,CMS就沒什么意義了,造成concurrent mode failure的原因是當minor GC進行時,舊生代所剩下的空間小于Eden區域+From區域的空間,或者在CMS執行老年帶的回收時有業務線程試圖將大的對象放入老年帶,導致CMS在老年帶的回收慢于業務對象對老年帶內存的分配。
解決這個問題的通用方法是調低觸發CMS GC執行的閥值,CMS GC觸發主要由CMSInitiatingOccupancyFraction值決定,默認情況是當舊生代已用空間為68%時,即觸發CMS GC,在出現concurrent mode failure的情況下,可考慮調小這個值,提前CMS GC的觸發,以保證舊生代有足夠的空間。
3.總結
promotion failed – concurrent mode failure
Minor GC后, 救助空間容納不了剩余對象,將要放入老年帶,老年帶有碎片或者不能容納這些對象,就產生了concurrent mode failure, 然后進行stop-the-world的Serial Old收集器。
解決辦法:-XX:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5 或者 調大新生代或者救助空間
concurrent mode failure
CMS是和業務線程并發運行的,在執行CMS的過程中有業務對象需要在老年帶直接分配,例如大對象,但是老年帶沒有足夠的空間來分配,所以導致concurrent mode failure, 然后需要進行stop-the-world的Serial Old收集器。
解決辦法:+XX:CMSInitiatingOccupancyFraction,調大老年帶的空間,+XX:CMSMaxAbortablePrecleanTime
★總結一句話:使用標記整理清除碎片和提早進行CMS操作。
”總結
以上是生活随笔為你收集整理的使用CMS垃圾收集器产生的问题和解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot 2.0 教程实战
- 下一篇: 手把手教你手动创建线程池