JVM中的垃圾收集器 -- CMS

 

一、概述

  • 簡介:

    • CMS收集器是一種以獲取最短回收停頓爲目標的收集器。
    • 由於GC線程在耗時最長的併發標記階段、併發清除階段、併發重設階段都是與用戶線程一起工作的,所以從總體上來說,CMS收集器的gc線程是與用戶線程併發執行的。
  • 觸發時機:

    • 第一次啓動的時機:

      • 老年代佔用率達到CMSInitiatingOccupancyFraction值(默認爲92%)時,jvm會啓動第一次CMS GC。
    • 之後啓動的時機:

      • jvm自動判斷。
    • 參數:

      • -XX:+UseCMSInitiatingOccupancyOnly
        • 命令JVM不要根據運行時收集的數據來判斷什麼時候開始gc,而是根據CMSInitiatingOccupancyFraction的值來判斷是否進行CMS收集,一般不會打開該開關。
      • -XX:CMSInitiatingOccupancyFraction
        • 設置觸發第一次CMS GC的閾值,老年代佔用率達到該值時,jvm會啓動第一次CMS GC,默認爲92%。
  • 收集算法:

    • 使用標記-清除算法。
  • GC類型:

    • major gc:只收集老年代。
  • 收集範圍

    • 整個老年代。

 

二、CMS GC過程:  

1、初始標記(CMS-initial-mark):

  • 標記對象:

    • 一個對象被標記,說明這個對象還存活着,不能被收集器回收。
  • 哪些對象會被標記:

    • 老年代中所有被根對象(GC Roots)直接引用的對象會被標記。
    • 老年代中被年輕代中存活對象直接引用的對象會被標記。
  • 標記方式:

    • 該階段需要Stop the word,因爲僅標記少量節點,所以該階段帶來的暫停時間很短。
  • 圖示:

    CMS initial mark

2、併發標記(CMS-concurrent-mark):

  • 哪些對象會被標記:

    • 從初始標記階段標記的對象出發,沿着引用鏈往下檢索(RootsTracing),標記出引用鏈上所有老年代中存活的對象。
  • 標記方式:

    • gc線程和應用程序線程是併發執行的,故在併發標記期間可能存在對象的漏標和錯標的問題。
  • 對象漏標:

    • 場景1:在併發標記期間有新的對象進入到老年代(新生代的對象晉升到老年代、在老年代中直接分配對象等),這些新進入老年代的對象沒有被標記。
    • 場景2:在併發標記期間用戶線程修改了存活對象A的某個字段:將這個字段指向了一個未被標記過的對象B,那麼此時對象B就由(併發標記開始時的)不可達變爲可達了,但是對象B卻沒有被標記。
  • 對象錯標:

    • 某個存活對象(如下圖的current obj)的某個字段引用着對象A,在併發標記期間用戶線程將該字段修改爲引用對象B,那麼此時對象A就由(併發標記開始時的)可達變爲不可達了,但是對象A卻已經被標記過了。

      CMS concurrent marking

  • 對象漏標和錯標的解決:

    • 在併發標記階段,一些對象的引用可能已經發生了變化(導致對象漏標或錯標),jvm會將這些引用發生變化的對象(包括新進入老年代的對象)所在的Card標記爲Dirty Card。

      CMS dirty cards

 

3、併發預清理(CMS-concurrent-preclean): 

  • 哪些對象會被標記:

    • 從Dirty Card中包含的對象開始,沿着引用鏈往下檢索(RootsTracing),標記出引用鏈上所有老年代中存活的對象。

  • 標記方式:

    • gc線程和應用程序線程併發執行。
    • 併發預清理的目的是爲了減少重新標記階段的工作,進而減少STW的時間。
  • Dirty Card恢復成正常的Card:

    • 當Dirty Card中所有對象的引用鏈都檢索完成後,這個Card的Dirty標識就被清除了。

      CMS concurrent preclean

 

4、可中斷的併發預清理階段( CMS-concurrent-abortable-preclean):

  • 觸發條件:

    • 在併發預清理階段執行完成之後,如果eden區的佔用量大於CMSScheduleRemarkEdenSizeThreshold(默認爲2M) ,則會觸發本階段執行。
  • 中斷條件:

    • eden區的佔用量大於CMSScheduleRemarkEdenPenetration(默認50%),則中斷本階段。
    • 本階段執行時間大於 CMSMaxAbortablePrecleanTime,則中斷本階段。
  • 哪些對象會被標記:

    • 和併發預清理階段標記的對象一樣。
  • 標記方式:

    • gc線程和應用程序線程併發執行,gc線程的標記工作可以被中斷。
    • 在可中斷的併發預清理階段,jvm期望發生一次minor gc,這樣年輕代中無用的對象就被回收掉了,進而可以減少remark階段掃描對象的數量。
    • 併發預清理的目的同樣也是爲了減少重新標記階段的工作,進而減少STW的時間。
  • 參數:

    • -XX:+CMSScheduleRemarkEdenSizeThreshold    
      •  eden區佔用量大於該值時(默認爲2M),觸發可中斷的併發預清理階段啓動。
    • -XX:CMSScheduleRemarkEdenPenetration
      • 表示eden區使用比例超過制定比例就結束該階段進入remark階段。
      • 默認50%,即:-XX:CMSScheduleRemarkEdenPenetration=50
    • -XX:CMSMaxAbortablePrecleanTime
      • 如果可中斷的預清理執行時間超過該值,那麼無論minor gc有沒有發生,該階段都會立即結束,然後進入remark階段,這樣是爲了避免因沒有等到minor gc而陷入無限等待。
      • 默認5秒,即:-XX:CMSMaxAbortablePrecleanTime=5000

 

5、重新標記(CMS-remark):

  • 哪些對象會被標記:

    • 重新標記在併發標記期間遺漏的對象。  
    • 新生代對象可能持有老年代中對象的引用,故CMS-remark階段掃描對象的範圍是整個堆,故堆(新生代+老年代)中對象的數目影響了Remark階段耗時。
      • 新生代:掃描新生代中所有的區域。
      • 老年代:根據gc roots沿着引用鏈掃描 。
  • 標記方式:

    • 因爲併發預清理是併發執行的,所以對象的引用可能會發生進一步的改變,故jvm在該階段需要STW,以確保在清理之前保持一個正確的對象引用視圖。
    • 一般重新標記階段的暫停時間是比較長的。
  • 參數:

    • -XX:+CMSScavengeBeforeRemark
      • 在remark前強制進行一次minor gc,這樣在一定程度上降低了重新標記階段對“遺漏”對象的掃描時間。
    • -XX:+CMSParallelRemarkEnabled
      • 可以並行remark,減少暫停的時間

6、併發清除(CMS-concurrent-sweep):

  • 哪些對象會被清除:

    • 未被標記的對象。
  • 清除方式:

    • gc線程和應用程序線程併發執行。
    • 使用標記-清除法回收清除老年代的垃圾對象(未被標記的對象)

      CMS concurrent sweep

  • 浮動垃圾:

    • 概念:在併發清除階段產生的垃圾稱爲浮動垃圾。
    • 說明:浮動垃圾在本次gc中是無法清除的,只能等到下次清理。

7、併發重設(CMS-concurrent-reset):

  • 工作內容:

    • 對記錄標記對象的表等數據結構做重置處理,爲下一次GC做準備。
  • 工作方式:

    • 併發重設線程和應用程序線程是併發執行的。

 

三、CMS GC失敗後的預案:

  • 如果CMS運行期間預留的內存無法滿足程序需要,那麼就會出現一次“Concurrent Mode Failure”失敗,此時JVM將啓動後備預案:
  • 使用Serial Old收集器重新對老年代進行gc,這樣一來,停頓時間就會很長。

 

四、CMS GC的兩種模式:

  • background模式:

    • 觸發條件:old的內存佔比超過多少的時候就可能觸發一次background式的cms gc
    • 其中background顧名思義是在後臺做的,也就是可以不影響正常的業務線程跑,這個過程會經歷CMS GC的所有階段,該暫停的暫停,該並行的並行,效率相對來說還比較高,畢竟有和業務線程並行的gc階段;
  • foreground模式:

    • 觸發條件:比如業務線程請求分配內存,但是內存不夠了,於是可能觸發一次cms gc
    • 這個過程就必須是要等內存分配到了線程才能繼續往下面走的,因此整個過程必須是STW的,因此CMS GC整個過程都是暫停應用的,但是爲了提高效率,它並不是每個階段都會走的,只走其中一些階段,這些省下來的階段主要是並行階段,Precleaning、AbortablePreclean,Resizing這幾個階段都不會經歷,其中sweep階段是同步的,但不管怎麼說如果走了類似foreground的cms gc,那麼整個過程業務線程都是不可用的,效率會影響挺大。
  • 參考:JVM源碼分析之SystemGC完全解讀 - 你假笨

 

五、一次CMS GC耗時統計

 

六、CMS收集器的替代品

JVM中的垃圾收集器

 

 參考:

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