JVM發生CMS GC的 5 種狀況,你知道的確定不全!

常常有同窗會問,爲啥個人應用 Old Gen 的使用佔比沒達到 CMSInitiatingOccupancyFraction 參數配置的閾值,就觸發了 CMS GC,表示很莫名奇妙,不知道問題出在哪?java

其實 CMS GC 的觸發條件很是多,不僅是 CMSInitiatingOccupancyFraction 閾值觸發這麼簡單。本文經過源碼全面梳理了觸發 CMS GC 的條件,儘量的幫你瞭解平時遇到的奇奇怪怪的 CMS GC 問題。算法

先拋出一些問題,來吸引你的注意力。bootstrap

  • 爲何 Old Gen 使用佔比僅 50% 就進行了一次 CMS GC?
  • Metaspace 的使用也會觸發 CMS GC 嗎?
  • 爲何 Old Gen 使用佔比很是小就進行了一次 CMS GC?

觸發條件後端

CMS GC 在實現上分紅 foreground collector 和 background collector。foreground collector 相對比較簡單,background collector 比較複雜,狀況比較多。安全

下面咱們從 foreground collector 和 background collector 分別來講明他們的觸發條件:性能優化

說明:本文內容是基於 JDK 8架構

說明:本文僅涉及 CMS GC 的觸發條件,至於算法的具體過程,以及何時進行 MSC(mark sweep compact)不在本文範圍併發

foreground collectorapp

foreground collector 觸發條件比較簡單,通常是遇到對象分配但空間不夠,就會直接觸發 GC,來當即進行空間回收。採用的算法是 mark sweep,不壓縮。less

background collector

說明 background collector 的觸發條件以前,先來講下 background collector 的流程,它是經過 CMS 後臺線程不斷的去掃描,過程當中主要是判斷是否符合 background collector 的觸發條件,一旦有符合的狀況,就會進行一次 background 的 collect。

 
  1. void ConcurrentMarkSweepThread::run() {  
  2. ...//省略  
  3. while (!_should_terminate) {  
  4. sleepBeforeNextCycle();  
  5. if (_should_terminate) break;  
  6. GCCause::Cause cause = _collector->_full_gc_requested ?  
  7. _collector->_full_gc_cause : GCCause::_cms_concurrent_mark;  
  8. _collector->collect_in_background(false, cause);  
  9. }  
  10. ...//省略  
  11. }  

每次掃描過程當中,先等 CMSWaitDuration 時間,而後再去進行一次 shouldConcurrentCollect 判斷,看是否知足 CMS background collector 的觸發條件。CMSWaitDuration 默認時間是 2s(常常會有業務遇到頻繁的 CMS GC,注意看每次 CMS GC 之間的時間間隔,若是是 2s,那基本就能夠判定是 CMS 的 background collector)。

 
  1. void ConcurrentMarkSweepThread::sleepBeforeNextCycle() {  
  2. while (!_should_terminate) {  
  3. if (CMSIncrementalMode) {  
  4. icms_wait();  
  5. if(CMSWaitDuration >= 0) {  
  6. // Wait until the next synchronous GC, a concurrent full gc  
  7. // request or a timeout, whichever is earlier.  
  8. wait_on_cms_lock_for_scavenge(CMSWaitDuration);  
  9. }  
  10. return;  
  11. } else {  
  12. if(CMSWaitDuration >= 0) {  
  13. // Wait until the next synchronous GC, a concurrent full gc  
  14. // request or a timeout, whichever is earlier.  
  15. wait_on_cms_lock_for_scavenge(CMSWaitDuration);  
  16. } else {  
  17. // Wait until any cms_lock event or check interval not to call shouldConcurrentCollect permanently  
  18. wait_on_cms_lock(CMSCheckInterval);  
  19. }  
  20. }  
  21. // Check if we should start a CMS collection cycle  
  22. if (_collector->shouldConcurrentCollect()) {  
  23. return;  
  24. }  
  25. // .. collection criterion not yet met, let's go back  
  26. // and wait some more  
  27. }  
  28. }  

那 shouldConcurrentCollect() 方法中都有哪些條件呢?

 
  1. bool CMSCollector::shouldConcurrentCollect() { 
  2. // 第一種觸發狀況 
  3. if (_full_gc_requested) { 
  4. if (Verbose && PrintGCDetails) { 
  5. gclog_or_tty->print_cr("CMSCollector: collect because of explicit " 
  6. " gc request (or gc_locker)"); 
  7. return true; 
  8. // For debugging purposes, change the type of collection. 
  9. // If the rotation is not on the concurrent collection 
  10. // type, don't start a concurrent collection. 
  11. NOT_PRODUCT( 
  12. if (RotateCMSCollectionTypes && 
  13. (_cmsGen->debug_collection_type() != 
  14. ConcurrentMarkSweepGeneration::Concurrent_collection_type)) { 
  15. assert(_cmsGen->debug_collection_type() != 
  16. ConcurrentMarkSweepGeneration::Unknown_collection_type, 
  17. "Bad cms collection type"); 
  18. return false; 
  19. FreelistLocker x(this); 
  20. // ------------------------------------------------------------------ 
  21. // Print out lots of information which affects the initiation of 
  22. // a collection. 
  23. if (PrintCMSInitiationStatistics && stats().valid()) { 
  24. gclog_or_tty->print("CMSCollector shouldConcurrentCollect: "); 
  25. gclog_or_tty->stamp(); 
  26. gclog_or_tty->print_cr(""); 
  27. stats().print_on(gclog_or_tty); 
  28. gclog_or_tty->print_cr("time_until_cms_gen_full %3.7f", 
  29. stats().time_until_cms_gen_full()); 
  30. gclog_or_tty->print_cr("free="SIZE_FORMAT, _cmsGen->free()); 
  31. gclog_or_tty->print_cr("contiguous_available="SIZE_FORMAT, 
  32. _cmsGen->contiguous_available()); 
  33. gclog_or_tty->print_cr("promotion_rate=%g", stats().promotion_rate()); 
  34. gclog_or_tty->print_cr("cms_allocation_rate=%g", stats().cms_allocation_rate()); 
  35. gclog_or_tty->print_cr("occupancy=%3.7f", _cmsGen->occupancy()); 
  36. gclog_or_tty->print_cr("initiatingOccupancy=%3.7f", _cmsGen->initiating_occupancy()); 
  37. gclog_or_tty->print_cr("metadata initialized %d", 
  38. MetaspaceGC::should_concurrent_collect()); 
  39. // ------------------------------------------------------------------ 
  40. // 第二種觸發狀況 
  41. // If the estimated time to complete a cms collection (cms_duration()) 
  42. // is less than the estimated time remaining until the cms generation 
  43. // is full, start a collection. 
  44. if (!UseCMSInitiatingOccupancyOnly) { 
  45. if (stats().valid()) { 
  46. if (stats().time_until_cms_start() == 0.0) { 
  47. return true; 
  48. } else { 
  49. // We want to conservatively collect somewhat early in order 
  50. // to try and "bootstrap" our CMS/promotion statistics; 
  51. // this branch will not fire after the first successful CMS 
  52. // collection because the stats should then be valid. 
  53. if (_cmsGen->occupancy() >= _bootstrap_occupancy) { 
  54. if (Verbose && PrintGCDetails) { 
  55. gclog_or_tty->print_cr( 
  56. " CMSCollector: collect for bootstrapping statistics:" 
  57. " occupancy = %f, boot occupancy = %f", _cmsGen->occupancy(), 
  58. _bootstrap_occupancy); 
  59. return true; 
  60. // 第三種觸發狀況 
  61. // Otherwise, we start a collection cycle if 
  62. // old gen want a collection cycle started. Each may use 
  63. // an appropriate criterion for making this decision. 
  64. // XXX We need to make sure that the gen expansion 
  65. // criterion dovetails well with this. XXX NEED TO FIX THIS 
  66. if (_cmsGen->should_concurrent_collect()) { 
  67. if (Verbose && PrintGCDetails) { 
  68. gclog_or_tty->print_cr("CMS old gen initiated"); 
  69. return true; 
  70. // 第四種觸發狀況 
  71. // We start a collection if we believe an incremental collection may fail; 
  72. // this is not likely to be productive in practice because it's probably too 
  73. // late anyway. 
  74. GenCollectedHeap* gch = GenCollectedHeap::heap(); 
  75. assert(gch->collector_policy()->is_two_generation_policy(), 
  76. "You may want to check the correctness of the following"); 
  77. if (gch->incremental_collection_will_fail(true /* consult_young */)) { 
  78. if (Verbose && PrintGCDetails) { 
  79. gclog_or_tty->print("CMSCollector: collect because incremental collection will fail "); 
  80. return true; 
  81. // 第五種觸發狀況 
  82. if (MetaspaceGC::should_concurrent_collect()) { 
  83. if (Verbose && PrintGCDetails) { 
  84. gclog_or_tty->print("CMSCollector: collect for metadata allocation "); 
  85. return true; 
  86. return false; 

上述代碼可知,從大類上分, background collector 一共有 5 種觸發狀況:

1.是不是並行 Full GC

指的是在 GC cause 是 gclocker 且配置了 GCLockerInvokesConcurrent 參數, 或者 GC cause 是javalangsystemgc(就是 System.gc()調用)and 且配置了 ExplicitGCInvokesConcurrent 參數,這時會觸發一次 background collector。

2.根據統計數據動態計算(僅未配置 UseCMSInitiatingOccupancyOnly 時) 未配置 UseCMSInitiatingOccupancyOnly 時,會根據統計數據動態判斷是否須要進行一次 CMS GC。

判斷邏輯是,若是預測 CMS GC 完成所須要的時間大於預計的老年代將要填滿的時間,則進行 GC。 這些判斷是須要基於歷史的 CMS GC 統計指標,然而,第一次 CMS GC 時,統計數據尚未造成,是無效的,這時會跟據 Old Gen 的使用佔比來判斷是否要進行 GC。

 
  1. if (!UseCMSInitiatingOccupancyOnly) { 
  2. if (stats().valid()) { 
  3. if (stats().time_until_cms_start() == 0.0) { 
  4. return true; 
  5. } else { 
  6. // We want to conservatively collect somewhat early in order 
  7. // to try and "bootstrap" our CMS/promotion statistics; 
  8. // this branch will not fire after the first successful CMS 
  9. // collection because the stats should then be valid. 
  10. if (_cmsGen->occupancy() >= _bootstrap_occupancy) { 
  11. if (Verbose && PrintGCDetails) { 
  12. gclog_or_tty->print_cr( 
  13. " CMSCollector: collect for bootstrapping statistics:" 
  14. " occupancy = %f, boot occupancy = %f", _cmsGen->occupancy(), 
  15. _bootstrap_occupancy); 
  16. return true; 

那佔多少比率,開始回收呢?(也就是 bootstrapoccupancy 的值是多少呢?) 答案是 50%。或許你已經遇到過相似案例,在沒有配置 UseCMSInitiatingOccupancyOnly 時,發現老年代佔比到 50% 就進行了一次 CMS GC,當時的你或許還一頭霧水呢。

 
  1. _bootstrap_occupancy = ((double)CMSBootstrapOccupancy)/(double)100; 
  2. //參數默認值 
  3. product(uintx, CMSBootstrapOccupancy, 50, 
  4. "Percentage CMS generation occupancy at which to initiate CMS collection for bootstrapping collection stats") 

3.根據 Old Gen 狀況判斷

 
  1. bool ConcurrentMarkSweepGeneration::should_concurrent_collect() const { 
  2. assert_lock_strong(freelistLock()); 
  3. if (occupancy() > initiating_occupancy()) { 
  4. if (PrintGCDetails && Verbose) { 
  5. gclog_or_tty->print(" %s: collect because of occupancy %f / %f ", 
  6. short_name(), occupancy(), initiating_occupancy()); 
  7. return true; 
  8. if (UseCMSInitiatingOccupancyOnly) { 
  9. return false; 
  10. if (expansion_cause() == CMSExpansionCause::_satisfy_allocation) { 
  11. if (PrintGCDetails && Verbose) { 
  12. gclog_or_tty->print(" %s: collect because expanded for allocation ", 
  13. short_name()); 
  14. return true; 
  15. if (_cmsSpace->should_concurrent_collect()) { 
  16. if (PrintGCDetails && Verbose) { 
  17. gclog_or_tty->print(" %s: collect because cmsSpace says so ", 
  18. short_name()); 
  19. return true; 
  20. return false; 

從源碼上看,這裏主要分紅兩類: (a) Old Gen 空間使用佔比狀況與閾值比較,若是大於閾值則進行 CMS GC 也就是"occupancy() > initiatingoccupancy()",occupancy 毫無疑問是 Old Gen 當前空間的使用佔比,而 initiatingoccupancy 是多少呢?

 
  1. _cmsGen ->init_initiating_occupancy(CMSInitiatingOccupancyFraction, CMSTriggerRatio); 
  2. ... 
  3. void ConcurrentMarkSweepGeneration::init_initiating_occupancy(intx io, uintx tr) { 
  4. assert(io <= 100 && tr <= 100, "Check the arguments"); 
  5. if (io >= 0) { 
  6. _initiating_occupancy = (double)io / 100.0; 
  7. } else { 
  8. _initiating_occupancy = ((100 - MinHeapFreeRatio) + 
  9. (double)(tr * MinHeapFreeRatio) / 100.0) 
  10. / 100.0; 

能夠看到當 CMSInitiatingOccupancyFraction 參數配置值大於 0,就是 「io / 100.0」;

當 CMSInitiatingOccupancyFraction 參數配置值小於 0 時(注意,默認是 -1),是 「((100 - MinHeapFreeRatio) + (double)(tr * MinHeapFreeRatio) / 100.0) / 100.0」,這究竟是多少呢?是 92%,這裏就不貼出具體的計算過程了,或許你已經在某些書或者博客中瞭解過,CMSInitiatingOccupancyFraction 沒有配置,就是 92,可是其實 CMSInitiatingOccupancyFraction 沒有配置是 -1,因此閾值取後者 92%,並非 CMSInitiatingOccupancyFraction 的值是 92。

(b) 接下來沒有配置 UseCMSInitiatingOccupancyOnly 的狀況

這裏也分紅有兩小類狀況:

當 Old Gen 剛由於對象分配空間而進行擴容,且成功分配空間,這時會考慮進行一次 CMS GC;

根據 CMS Gen 空閒鏈判斷,這裏有點複雜,目前也沒整清楚,好在按照默認配置其實這裏返回的是 false,因此默認是不用考慮這種觸發條件了。

4.根據增量 GC 是否可能會失敗(悲觀策略)

什麼意思呢?兩代的 GC 體系中,主要指的是 Young GC 是否會失敗。若是 Young GC 已經失敗或者可能會失敗,JVM 就認爲須要進行一次 CMS GC。

 
  1. bool incremental_collection_will_fail(bool consult_young) { 
  2. // Assumes a 2-generation system; the first disjunct remembers if an 
  3. // incremental collection failed, even when we thought (second disjunct) 
  4. // that it would not. 
  5. assert(heap()->collector_policy()->is_two_generation_policy(), 
  6. "the following definition may not be suitable for an n(>2)-generation system"); 
  7. return incremental_collection_failed() || 
  8. (consult_young && !get_gen(0)->collection_attempt_is_safe()); 

咱們看兩個判斷條件,「incrementalcollectionfailed()」 和 「!getgen(0)->collectionattemptissafe()」 incrementalcollectionfailed() 這裏指的是 Young GC 已經失敗,至於爲何會失敗通常是由於 Old Gen 沒有足夠的空間來容納晉升的對象。

!getgen(0)->collectionattemptissafe() 指的是新生代晉升是否安全。 經過判斷當前 Old Gen 剩餘的空間大小是否足夠容納 Young GC 晉升的對象大小。 Young GC 到底要晉升多少是沒法提早知道的,所以,這裏經過統計平均每次 Young GC 晉升的大小和當前 Young GC 可能晉升的最大大小來進行比較。

 
  1. //av_promo 是平均每次 YoungGC 晉升的大小,max_promotion_in_bytes 是當前可能的最大晉升大小( eden+from 當前使用空間的大小) 
  2. bool res = (available >= av_promo) || (available >= max_promotion_in_bytes); 

5.根據 meta space 狀況判斷

這裏主要看 metaspace 的 shouldconcurrent_collect 標誌,這個標誌在 meta space 進行擴容前若是配置了 CMSClassUnloadingEnabled 參數時,會進行設置。這種狀況下就會進行一次 CMS GC。所以常常會有應用啓動不久,Old Gen 空間佔比還很小的狀況下,進行了一次 CMS GC,讓你很莫名其妙,其實就是這個緣由致使的。

這裏推薦一下個人Java後端技術羣:834962734,羣裏有(分佈式架構、高可擴展、高性能、高併發、性能優化、Spring boot、Redis、ActiveMQ、等學習資源)進羣免費送給每一位Java小夥伴,無論你是轉行,仍是工做中想提高本身能力均可以,歡迎進羣一塊兒深刻交流學習!

總結

本文梳理了 CMS GC 的 foreground collector 和 background collector 的觸發條件,foreground collector 的觸發條件相對來講比較簡單,而 background collector 的觸發條件比較多,分紅 5 大種狀況,各大種狀況種還有一些小的觸發分支。尤爲是在沒有配置 UseCMSInitiatingOccupancyOnly 參數的狀況下,會多出不少種觸發可能,通常在生產環境是強烈建議配置 UseCMSInitiatingOccupancyOnly 參數,以便於可以比較肯定的執行 CMS GC,另外,也方便排查 GC 緣由。

相關文章
相關標籤/搜索