1. 程式人生 > >JVM發生CMS GC的 5 種情況,你知道的肯定不全!

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

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

其實 CMS GC 的觸發條件非常多,不只是 CMSInitiatingOccupancyFraction 閾值觸發這麼簡單。本文通過原始碼全面梳理了觸發 CMS GC 的條件,儘可能的幫你瞭解平時遇到的奇奇怪怪的 CMS GC 問題。

先丟擲一些問題,來吸引你的注意力。

  • 為什麼 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 collector

foreground collector 觸發條件比較簡單,一般是遇到物件分配但空間不夠,就會直接觸發 GC,來立即進行空間回收。採用的演算法是 mark sweep,不壓縮。

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,另外,也方