JVM學習筆記24 CMS 垃圾收集器

bilibili-JVM學習筆記24 CMS 垃圾收集器
The Java Virtual Machine Specification - Java SE 8 Edition

JVM學習筆記18 字節碼知識總結
JVM學習筆記19 JVM內存空間
JVM學習筆記20 jvisualvm
JVM學習筆記21 java 工具
JVM學習筆記22 垃圾回收理論知識
JVM學習筆記23內存泄漏 和 VM參數設置

基於 java 1.8.0

安全點與安全區域詳解 83

Concurrent Mark Sweep --> CMS

  • 枚舉根節點
    • 當執行系統停頓下來後,並不需要一個不漏地檢查完所有執行上下文和全局的引用位置,虛擬機應當是有辦法直接得知哪些地方存放着對象引用。在 HotSpot 的實現中,使用一組稱爲 OopMap 的數據結構來達到這個目的的;
  • 安全點 Safepoint
    • 在 OopMap 的協助下,HotSpot 可以快速且準確地完成 GC Roots 枚舉,但一個很現實的問題隨之而來:可能導致引用關係變化,或者說 OopMap 內容變化的指令非常多,如果爲每一條指令都生成對應的 OopMap ,那將會需要大量的額外空間,這樣 GC 的空間成本將會變得更高;
    • 實際上,HotSpot 並沒有爲每條指令都生成 OopMap ,而只是在特定的位置記錄了這些信息,這些位置稱爲安全點Safepoint),即程序執行時並非在所有地方都能停頓下來開始 GC,只有在到達安全點時才能暫停。
    • Safepoint 的選定既不能太少以至於讓 GC 等待時間太長,也不能過於頻繁以至於過分增大運行時的負載。所以,安全點的選定基本上是以是否具有讓程序長時間執行的特徵爲標準進行選定的–因爲每條指令執行的時間非常短暫,程序不太可能因爲指令流長度太長這個原因而過長時間運行,長時間執行的最明顯特徵就是指令序列複用,例如方法調用、循環跳轉、異常跳轉等,所有具有這些功能的指令纔會產生 Safepoint 。
    • 對於 Safepoint ,另一個需要考慮的問題是如何在 GC 發生時讓所有線程(不包括執行 JNI 調用的線程)都跑到最近的安全點上再停頓下來?
      • 搶佔式中斷 Preemptive Suspension
        • 不需要線程的執行代碼主動去配合,在 GC 發生時,首先把所有的線程全部中斷,如果有線程中斷的地方不在安全點上,就恢復線程,讓它跑到安全點上;
      • 主動式中斷 Voluntary Suspension
        • 當 GC 需要中斷線程的時候,不直接對線程進行操作,僅僅簡單的設置一個標誌,各個線程執行時主動去輪詢這個標誌,發現中斷標誌爲真時就自己中斷掛起。輪詢標誌的地方和安全點是重合的,另外再加上創建對象需要分配內存的地方。
      • 現在幾乎沒有虛擬機採用搶佔式中斷來暫停線程從而響應 GC 事件;
  • 安全區域 Safe Regin
    • 在使用 Safeponint 似乎已經完美地解決了如何進入 GC 的問題,但實際上情況卻並不一定。Safepoint 機制保證了程序執行時,在不太長的時間內就會遇到可進入 GC 的 Safepoint 。但如果程序在不執行的時候呢?所謂程序不執行就是沒有分配 CPU 時間,典型的例子就是線程處於 Sleep 狀態或者 Blocked 狀態,這時候線程無法響應 JVM 的中斷請求,JVM 也顯然不太可能等待線程重新分配 CPU 時間。對於這種情況,就需要安全區域來解決了。
    • 在線程執行到安全區域中的代碼時,首先標識自己已經進入了安全區域,那麼,當在這段時間裏 JVM 要發起 GC 時,就不用管標識自己爲安全區域狀態的線程了。在線程要離開安全區域時,它要檢查系統是否已經完成了根節點枚舉(或者是整個 GC 過程),如果完成了,那麼線程就繼續執行,否則它就必須等待直到收到可以安全離開安全區域的信號爲止。

CMS垃圾收集器深入講解 84

CMS 垃圾收集器

  • CMS ,以獲取最短回收停頓時間爲目標,多數應用於互聯網站或者B/S系統的服務器端上;
  • CMS 是基於 標記-清除 算法實現的,整個過程分爲 4 個步驟:
    • 初始標記(CMS inital mark)
    • 併發標記(CMS concurrent mark)
    • 重新標記(CMS remark)
    • 併發清除(CMS concurrent sweep)
  • CMS
    • 其中,初始標記、重新標記 這兩個步驟仍然需要 Stop The World
    • 初始標記只是標記一下 GC Roots 能直接關聯到的對象,速度很快;
    • 併發標記階段就是進行 GC Roots Tracing 的過程;
    • 重新標記階段則是爲了修正併發標記期間因用戶程序繼續運作而導致標記採產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍微長一些,但遠比並發標記的時間要短。
  • CMS 收集器的運作步驟如下圖所示,在整個過程中耗時最長的併發標記和併發清除過程收集器線程都可以與用戶線程一起工作,因此,從總體上看,CMS 收集器的內存回收過程是與用戶線程一起併發執行的。

在這裏插入圖片描述

  • CMS 優點
    • 併發收集
    • 低停頓
  • CMS 缺點
    • CMS 對 CPU 資源非常敏感;
    • 無法處理浮動垃圾 Floating Garbage,可能出現 Concurrent Mode Failure 失敗而導致另一次 Full GC 的產生,可能引發串行 Full GC。如果在應用中老年代增長不是太快,可以適當調高參數 -XX:CMSInitiatingOccuoancyFraction 的值來提高觸發百分比,以便降低內存回收次數從而獲取更好的性能。要是 CMS 運行期間預留的內存無法滿足程序需要時,虛擬機將啓動後備預案:臨時啓動 Serial Old 收集器來重新進行老年代的垃圾收集,這樣停頓時間就更長了。所以說參數 -XX:CMSInitiatingOccuoancyFraction 設置的太高很容易導致大量 Concurrent Mode Failure 失敗,性能反而降低。
    • 收集結束時會產生大量的空間碎片,空間碎片過多時,將會給大對象分配帶來很大麻煩,往往出現老年代還有很大空間剩餘,但是無法找到足夠大的連續空間來分配當前對象,不得不提前進行一次 Full GC,CMS 收集器提供了一個參數 : -XX:+UseCMSCompactAtFullCollection 開關(默認開啓),用於在 CMS 收集器頂不住要進行 Full GC 時開啓內存碎片的合併整理過程,內存整理的過程是無法併發的,空間碎片問題沒有了,但停頓時間不得不變長。
    • 對於堆比較大的應用,GC 的時間難以預估。
  • 空間分配擔保
    • 在發生 Minor GC 之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果這個條件成立,那麼 Minor GC 可以確保是安全的。
    • 當大量對象在 Minor GC 後仍然存活,這就需要老年代進行空間分配擔保,把 Survivor 無法容納的對象直接進入老年代。如果老年代判斷到剩餘空間不足,則進行一次 Full GC。(根據以往每一次回收晉升到老年代對象容量的平均值作爲經驗值)。

JDK DocumentHotSpot Virtual Machine Garbage Collection Tuning Guide

CMS 收集器完整收集步驟:

  • Phase 1 : Initial Mark
    • STW
    • 標記那些直接被 GC Roots 引用或者被年輕代存活對象所引用的所有對象
    • 在這裏插入圖片描述
  • Phase 2 : Concurrent Mark
    • 垃圾收集器會遍歷老年代,然後標記所有存活的對象,他會根據上個階段找到的 GC Roots 遍歷查找。併發標記階段,它會與用戶的應用程序併發運行。並不是老年代所有的存活對象都會被標記,因爲在標記期間用戶的程序可能會改變一些引用。
    • 在這裏插入圖片描述
    • 與階段1中圖相比,會發現有一個對象的引用已經發生了改變。
  • Phase 3 : Concurrent Preclean
    • 與用戶線程併發執行
    • 在階段2,一些對象的引用可能會發生變化,但是這種情況發生時,JVM 會將包含這個對象的區域(Card)標記爲 Dirty ,這就是 Card Marking
    • 在 pre-clean 階段,那些能夠從 Dirty 對象到達的對象也會被標記,這個標記做完後,dirty card 標記就會被清除。
    • 在這裏插入圖片描述
  • Phase 4 : Concurrent Abortable Preclean
    • 是爲了儘量承擔 STW 中最終標記階段的工作,這個階段持續時間依賴於很多的因素,由於這個階段是在重複做很多相同的工作,直接滿足一些條件(比如:重複迭代的次數、完成的工作量或者時鐘時間等)
  • Phase 5 : Final Remark
    • STW
    • 標記老年代所有的存活對象,由於之前的階段是併發執行的,gc 線程可能跟不上應用程序的變化,爲了完成標記老年代所有存活對象的目標,STW 就非常有必要了。
    • 通常 CMS 的 Final Remark 階段會在年輕代儘可能乾淨的時候運行,目的是爲了減少連續 STW 發生的可能性(年輕代存活對象過多的話,也會導致老年代設計的存活對象過多),這個階段會比前面的幾個階段更復雜一些。
    • 經歷過這五個階段之後,老年代所有存活的對象都被標記完成了。
  • Phase 6 : Concurrent Sweep
    • 清除那些不再使用的對象,回收內存
  • Phase 7 : Concurrent Reset
    • 重置 CMS 內部的數據結構,爲下次的 GC 做準備。

CMS 通過將大量工作分散到併發處理階段來減少 STW 時間;

實例透徹分析CMS垃圾收集器執行過程 85

package new_package.jvm.p83;

/**
 * -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC
 */
public class MyTest {

    public static void main(String[] args) {

        int sizeM = 1024 * 1024;
        byte[] bytes1 = new byte[4 * sizeM];
        System.out.println("11111111");
        byte[] bytes2 = new byte[4 * sizeM];
        System.out.println("22222222");
        byte[] bytes3 = new byte[4 * sizeM];
        System.out.println("33333333");
        byte[] bytes4 = new byte[2 * sizeM];
        System.out.println("44444444");

        System.out.println("hello world");
    }
}

11111111
[GC (Allocation Failure) [ParNew: 6616K->595K(9216K), 0.0069552 secs] 6616K->4693K(19456K), 0.0069915 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
22222222
[GC (Allocation Failure) [ParNew: 4846K->108K(9216K), 0.0040079 secs] 8944K->8843K(19456K), 0.0040301 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8734K(10240K)] 12939K(19456K), 0.0001778 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
33333333
44444444
hello world
[CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Final Remark) [YG occupancy: 6492 K (9216 K)][Rescan (parallel) , 0.0000792 secs][weak refs processing, 0.0000071 secs][class unloading, 0.0002851 secs][scrub symbol table, 0.0005528 secs][scrub string table, 0.0001244 secs][1 CMS-remark: 8734K(10240K)] 15227K(19456K), 0.0010957 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation  [CMS-concurrent-reset-start]
 total 9216K, used 6656K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  79% used [0x00000007bec00000, 0x00000007bf264d60, 0x00000007bf400000)
  from space 1024K,  10% used [0x00000007bf400000, 0x00000007bf41b3d8, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
[CMS-concurrent-reset: 0.000/0.000 secs] concurrent mark-sweep generation [Times: user=0.00 sys=0.00, real=0.00 secs] 
 total 10240K, used 8733K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
 Metaspace       used 3241K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 346K, capacity 388K, committed 512K, reserved 1048576K

JVM垃圾回收重點內容階段性複習與總結 86

CMS垃圾回收器重要內容回顧與總結 87

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