JVM垃圾收集器專題

Awecoder發表於2022-01-02

垃圾收集器:利用垃圾收集演算法,實現垃圾回收的實踐落地。

1 HotSpot垃圾回收器

HotSpot垃圾回收器有多個,可以配合使用。

image

1.1 垃圾回收的一些術語

術語: Stop the world

簡寫為STW,也叫全域性停頓,Java程式碼停止執行,native程式碼繼續執行,但不能與JVM進行互動。

STW主要是為了GC操作的準確性和效率。使用者執行緒不停止的話,會不斷有新物件和垃圾物件產生,假設沒有STW,會導致GC時間過長,錯誤清理新物件等情況。

原因:多半由於垃圾回收導致;也可能是Dump執行緒、死鎖檢查、Dump堆等導致

危害:服務停止、沒有響應主從切換(對於高可用環境,如果停頓時間過長,會引發主從之間的切換)、危害生產安全。

因此,儘量縮短Stop the world的時間

術語--並行收集 VS 併發收集

並行收集:指多個垃圾收集執行緒並行工作,但是在收集的過程中,使用者執行緒(你的業務執行緒)還是處於等待狀態的

併發收集:指使用者執行緒與垃圾收集執行緒同時工作

術語-- 吞吐量

CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值

公式:執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)

2 新生代垃圾收集器(與老年代收集器配合使用)

2.1 Serial收集器

Serial 收集器是最基本、發展歷史最悠久的收集器,採用複製演算法。採用單執行緒操作,收集過程中全程STW

image

適用場景有:

  • 客戶端程式,應用以-client 模式執行時,預設使用的就是Serial(java -client -jar 執行)
  • 單核機器

2.2 ParNew收集器

Serial收集器的多執行緒版,除使用多執行緒以外,其他和Serial收集器一樣,包括:JVM引數、Stop the world表現、垃圾收集演算法都是一樣的。可使用 -XX:ParallelGCThreads設定垃圾收集的執行緒數。主要和CMS垃圾收集器配合使用。

image

2.3 ParallelScavenge收集器

ParallelScavenge收集器,是吞吐量優先收集器,也是採用複製演算法,也是多執行緒的。執行過程與ParNew收集器類似。

Parallel Scavenge收集器適用於注重吞吐量的場景。

Parallel Scavenge收集器特點

可以達到一個可控制的吞吐量

  • -XX:MaxGCPauseMillis:控制最大的垃圾收集停頓時間(盡力)

  • -XX:GCTimeRatio:設定吞吐量的大小,取值0-100,系統花費不超過1/(1+n)的時間用於垃圾收集

自適應GC策略:可用-XX:+UseAdptiveSizePolicy開啟

  • 開啟自適應策略後,無需手動設定新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRatio)等引數虛擬機器會自動根據系統的執行狀況收集效能監控資訊,動態地調整這些引數,從而達到最優的停頓時間以及最高的吞吐量。

3 老年代收集器(與新生代收集器配合使用)

3.1 Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,單執行緒,採用的是標記-整理演算法,垃圾收集過程Stop The World。

可以與上面三個新生代收集器配合使用;當CMS收集器出現故障時,作為後備處理器。

3.2 Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,只能與Parallel Scavenge收集器配合使用,同樣是注重對吞吐量要求較高的場景。

Parallel Old收集器,採用多執行緒,標記-整理演算法,垃圾收集過程Stop The World。

3.3 CMS收集器(JDK9廢棄)

CMS: Concuerrent mark sweep(併發標記收集器),採用標記-清除演算法。

image

CMS執行過程的七個階段:

  1. 初始標記:標記GC Roots能直接關聯的物件,觸發短暫STW操作
  2. 併發標記:找出所有GC Roots能關聯的物件。併發執行,不會觸發STW。
  3. 併發預清理階段(可選):重新標記在併發標記階段引用被更新的物件,從而減少後面重新標記的工作量。併發執行,不會觸發STW。可使用-XX:-CMSPrecleaningEnabled關閉併發預清理階段,預設開啟。
  4. 併發可中止的預清理階段(可選):和併發預清理做的事情一樣,併發執行,無 Stop The World。預清理後,當Eden的使用量大於CMSScheduleEdenSizeThreshold的閾值(預設2M)時,才會執行該階段。主要作用是允許我們能夠控制預清理階段的結束時機。
  5. 重新標記階段:修正併發標記期間,因為使用者程式繼續執行,導致標記發生變動的那些物件的標記。一般來說,重新標記花費的時間會比初始標記階段長一些,但比並發標記的時間短。存在 Stop The World。
  6. 併發清除階段:基於標記結果,清除垃圾物件。併發執行,無STW。使用標記清除演算法,因為標記整理涉及資料位置,併發情況難以實現。
  7. 併發重置階段:清除本地CMS GC的上下文資訊,為下次GC做準備。

CMS收集器的優缺

優點:STW時間較短,大多數過程併發執行。

缺點:

  1. 垃圾回收併發執行時,會與使用者執行緒有一定CPU資源爭搶,影響吞吐量;
  2. CMS只在標記的時候STW,清除時無STW,無法處理浮動垃圾;
  3. 採用標記-清除法,導致記憶體碎片的產生;
    1. 可以使用UseCMSCompactAtFullCollection:在完成Full GC後是否要進行記憶體碎片整理,預設開啟。CMSFullGCsBeforeCompaction:進行幾次Full GC後就進行一次記憶體碎片整理,預設是0。
  4. 無法等到老年代幾乎滿了才開始垃圾收集。
    1. 執行過程中多個階段沒有STW操作,會不斷有物件晉升到老年代,當老年代預留的記憶體不夠時,會導致Concurrent Mode Failure,從而切換成後備老年代收集器 Serial Old。
    2. 可使用 CMSInitiatingOccupancyFraction設定老年代佔比達到多少就觸發垃圾收集,預設68%。

CMS適用於希望系統停頓時間短,響應速度快的場景,例如Web。

擴充套件:併發預處理、併發中斷預處理

1、首先,CMS是一個關注停頓時間,以回收停頓時間最短為目標的垃圾回收器。併發預處理階段做的工作是標記,重標記需要STW(Stop The World),因此重標記的工作儘可能多的在併發階段完成來減少STW的時間。此階段標記從新生代晉升的物件、新分配到老年代的物件以及在併發階段被修改了的物件。
2、併發可中斷預清理(Concurrent precleaning)是標記在併發標記階段引用發生變化的物件,如果發現物件的引用發生變化,則JVM會標記堆的這個區域為Dirty Card。那些能夠從Dirty Card到達的物件也被標記(標記為存活),當標記做完後,這個Dirty Card區域就會消失。CMS有兩個引數:CMSScheduleRemarkEdenSizeThreshold、CMSScheduleRemarkEdenPenetration,預設值分別是2M、50%。兩個引數組合起來的意思是預清理後,eden空間使用超過2M時啟動可中斷的併發預清理(CMS-concurrent-abortable-preclean),直到eden空間使用率達到50%時中斷,進入重新標記階段。

4 G1收集器

G1(Garbge First),面向服務端應用,可以同時用於新生代和老年代。

image

G1收集器使用Region作為單位,包括四種型別,分別為Eden、Survior、Old和Humongous,通過引數-XX:G1HeapRegionSize指定Region的大小,取值範圍為1MB ~ 32 MB之間。

前三種Region仍然是伊甸園、存活區、老年代,Humongous用來儲存大物件,物件過大可以儲存到連續的Humongous中。Old和Humongous同屬於老年代。

G1收集器的設計思想將記憶體分成很多小塊(Region),跟蹤每個Region中垃圾堆積的價值大小,構建一個優先列表,根據允許的收集時間,優先回收價值最高的Region。其中價值大小指的是回收該Region能獲得的空間大小以及回收所需要的時間成本。

4.1 G1收集器的垃圾回收機制

主要分為三種,Young GC、Mixed GC、Full GC。

4.1.1 Young GC

所有Eden Region都滿了的時候,就會觸發Young GC。

  • Eden中存活的物件轉移到Survior Region;
  • 原先 Survivor Region中的物件轉移到新的 Survivor Region中,或者晉升到Old Region。
  • 空閒 Region會被放入空閒列表中,等待下次被使用。

4.1.2 Mixed GC

老年代大小佔整個堆的百分比達到一定閾值(可用-XX:InitiatingHeapOccupancyPercent指定,預設45%),就觸發Mixed GC,會回收所有 Young Region,同時回收部分 Old Region。

image

執行過程分為四步:

  1. 初始標記:跟CMS類似,標記處GC Roots能直接關聯到的物件,存在短暫STW

  2. 併發標記:跟CMS類似,找出所有GC Roots能關聯的物件。併發執行,不會觸發STW。

  3. 最終標記:更新在併發標記期間引起的變更,存在STW

  4. 篩選回收:首先對各個Region的回收價值和成本排序,根據使用者所期望的停頓時間指定回收計劃,篩選出合適的Region回收。停頓時間:MaxGCPauseMillis。

    回收過程:將Region的存活物件複製到空閒Region中,然後刪除原Region,是複製演算法,無記憶體碎片,過程存在STW。

G1收集器Mixed GC除了併發標記以外的過程都是STW的,由於一次只回收一部分Region,所以停頓時間可控。

4.1.3 Full GC

複製物件記憶體不夠,或者無法分配足夠的記憶體(例如大物件無法分配連續的記憶體),就會觸發Full GC。Full GC機制採用單執行緒的Serial Old模式。

因此G1收集器的優化原則是,儘可能的減少Full GC。

4.2 G1優化原則:減少Full GC

  1. 增加預留記憶體(增大-XX:G1 Reserve Percent,預設為堆的10%)
  2. 更早地回收垃圾(減少- XX: InitiatingHeapOccupancyPercent,老年代達到該值就觸發 Mixed GC,預設45%
  3. 增加併發階段使用的執行緒數(增大-XX: ConcGCThreads)

上面三點大白話總結:多留一點記憶體,有記憶體可以分配;有垃圾早點回收;垃圾回收快點。

4.3 G1收集器的使用

G1收集器作用於整個堆,可以控制停頓時間(MaxGCPauseMillis=200),並且沒有記憶體碎片。

G1收集器佔用記憶體較大(通常需要6G以上),可以代替CMS收集器。對於JDK8,主要根據記憶體選擇,記憶體小於6G,選CMS;記憶體大於6G,使用G1。CMS在JDK9中被廢棄,高於JDK8的版本可以選G1。

5 實驗收集器

截止目前JDK14依然處於實驗狀態

  • Shenandoah(IBM開發,是ZGC競品,進入OpenJDK,被Oracle JDK12剔除。)

  • ZGC(一款革命性的收集器)

  • Epsilon:不幹活的垃圾收集器。

    • 控制記憶體分配,但是不執行垃圾回收工作,堆耗盡就直接關閉JVM。

6 如何選擇垃圾收集器?

理論出發主要有下面幾點。

  1. 專案主要關注矛盾點。例如吞吐量(Parallel)、訪問延遲(CMS/G1)、應用啟動速度
  2. 基礎設施:CPU、記憶體等
  3. JDK版本。例如JDK6沒有G1,Oracle JDK沒有Shenandoah。

相關文章