《深入理解Java虛擬機器器》第三章讀書筆記(三)——經典垃圾回收器

2023-02-04 18:01:04

系列文章目錄和關於我

一丶概述

上圖展示了 經典的垃圾回收器,其中Serial,ParNew,Parallel Scavenge(途中的Parallel) 作用在新生代Serial Old CMS,Parallel Old作用在老年代,這些垃圾回收器顏色相同表示通常搭配使用。G1,ZGC,Shenandoah垃圾收集器則拋棄了分代收集理論作用於整堆。下面將介紹這些垃圾回收器

二丶Serial 和 Serial Old

Serial 是作用在新生代的,使用標記複製演演算法單執行緒進行垃圾收集的垃圾回收器(單執行緒意味著:回收垃圾的時候使用單個執行緒,並且回收垃圾的時候工作執行緒也停止)。它是使用者端模式下預設的垃圾收集器,優點點在於簡單高效,對於記憶體資源受限的環境,它是所有收集器裡額外記憶體消耗最小的,並且沒用執行緒切換的開銷,因此在單核CPU下Serial表現甚至優於多核CPU

Serial OldSerial的老年代版本,使用標記整理演演算法,除了和Serial搭配使用之外,它還是CMS老年代垃圾回收失敗時的後備預案

  • -XX:+UseSerialGC :讓虛擬機器器使用Serial+Serial Old 進行垃圾回收
  • -XX:SurvivorRatio:控制Survivor 和 Eden的比值,如-XX:SurvivorRatio=8 表示Eden:Survivor=8:1

三丶ParNew

ParNew 可用認為是Serial的並行版本,作用於新生代,使用標記複製演演算法,它是啟用CMS垃圾回收器之後預設的新生代垃圾回收器。

ParNew在單核CPU處理器環境中,不會比Serial更高效,因為它存線上程互動的開銷。使用-XX:ParallelGCThreads=n限制垃圾收集的執行緒數

使用 -XX:+UseParNewGC 指定使用ParNew + Serial Old的組合,二者配合工作如下圖。

四丶Parallel Scavenge

Parallel Scavenge 和ParNew 類似,作用於新生代,使用標記複製演演算法。但是它是專注於吞吐量的垃圾收集器。(吞吐量 = 使用者程式碼執行時間 / 處理器總消耗時間(使用者程式碼時間+GC時間)

  • -XX:+UseParallelGC 虛擬機器器在Server模式下的預設值,開啟後,使用 Parallel Scavenge + Serial Old的組合

  • -XX:MaxGCPauseMillis=n 收集器儘可能保證單次記憶體回收停頓的時間不超過這個值,但是並不保證不超過該值。(如果設定太小將導致Minor GC頻繁)

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

  • -XX:+UseAdaptiveSizePolicy 開啟後,無需人工指定新生代的大小(-Xmn)、 Eden和Survivor的比例(-XX:SurvivorRatio)以及晉升老年代物件的年齡(-XX:PretenureSizeThreshold)等引數,收集器會根據當前系統的執行情況自動調整

五丶Parallel Old

Parallel Old 是Parallel Scavenge的老年代版本,多執行緒,使用標記整理演演算法.-XX:+UseParallelGC 使用 Parallel Scavenge + Serial Old的搭配方式,並不能重複發揮伺服器的多處理器並行處理能力。Parallel Old 出現後注重吞吐量的場景可用使用-XX:+UseParallelOldGC選擇Parallel Scavenge + Parallel Old進行搭配使用

六丶CMS

CMS是一種以獲取最短回收停頓時間為目標收集器使用標記清除演演算法回收老年代。適合在關注服務響應速度系統停頓時間的場景中使用,如B/S 系統。CMS的優勢在於它使用了三色標記演演算法,實現了垃圾回收和使用者執行緒的並行執行(初始標記和並行重新標記還是需要Stop The World)

1.CMS回收流程

  • 初始標記

    指的是尋找所有被 GCRoots 參照的物件,該階段需要Stop the World ,這個步驟僅僅只是標記一下 GC Roots 能直接關聯到的物件,並不需要做整個參照的掃描,因此速度很快。

  • 並行標記

    指的是對「初始標記階段」標記的物件進行整個參照鏈的掃描,該階段不需要Stop the World。 對整個參照鏈做掃描需要花費非常多的時間,因此通過垃圾回收執行緒與使用者執行緒並行執行,可以降低垃圾回收的時間。這也是 CMS 垃圾回收器能極大降低 GC 停頓時間的核心原因,但這也帶來了一些問題,即:並行標記的時候,參照可能發生變化,因此可能出現錯殺(是垃圾的物件沒用被標記為垃圾,產生浮動垃圾),和錯標(不是垃圾的物件被標記為垃圾的問題

  • 重新標記

    指的是對並行標記階段出現的問題進行校正,該階段需要Stop the World。 這一階段解決並行標記階段發生錯標的問題。

  • 並行清除

    指的是將標記為垃圾的物件進行清除,該階段不需要Stop the World。垃圾回收執行緒和使用者執行緒並行進行。(這一步使用標記清除演演算法,所有可以和使用者執行緒並行,但是標記清除會產生較多的記憶體碎片)

2.CMS 優點

並行收集,低停頓

將原本的標記資訊,分為了初始標記,並行標記,重新標記,其中最為耗時的並行標記可以和使用者執行緒並行,從而提高了垃圾回收的效率。垃圾回收的階段(並行清除)也可以和使用者執行緒並行,實現了垃圾回收的低停頓。

3.CMS 缺點

  • 對 CPU 資源消耗較大

    雖然使用多執行緒進行垃圾收集提升效率,但是也真是由於多執行緒垃圾收集流程會佔用一些處理器的計算能力而導致應用程式變慢,降低吞吐量。

    預設情況下 CMS 啟用的垃圾回收執行緒數是(CPU數量 + 3)/4,當 CPU 數量越大時,啟用的垃圾回收執行緒數佔比就越小。

    但如果 CPU 數量不足四個的時候,垃圾回收執行緒佔用就達到了 50%,也就是說需要拿 50% 的 CPU 時間來進行垃圾回收。這就會極大地降低系統的吞吐量,這是讓人無法接受的情況。

  • 無法處理浮動垃圾。 由於 CMS 並行標記階段會發生錯標的情況,因此會有一些本該回收的垃圾物件無法被回收。此外在 CMS 進行並行清理的時候,使用者執行緒同時在執行,也會產生一些浮動垃圾。因此對於 CMS 回收器來說,其需要留出一些空間給這些浮動垃圾儲存,等到下一次垃圾回收才能進行清理。

    可以通過 -XX:CMSInitiatingOccupancyFraction 引數調節老年代空間使用多少之後觸發CMS進行老年代的回收。

    如果在 CMS 執行期間發現預留的記憶體無法滿足程式需要,就會提示Concurrent Mode Failure錯誤。此時虛擬機器器採用後備方案:臨時啟用 Serial Old 回收器來重新進行老年代的垃圾回收,這時候 Stop the World 的時間可能就會很長了

  • 產生空間碎片。 由於 CMS 是基於標記-清除演演算法實現的回收器,因此其會產生很多空間碎片,這會導致給大物件分配的時候很麻煩,會提前觸發 Full GC。可以使用 -XX:+UseCMSCompactAtFullCollection(預設是開啟的) 引數來讓無法分配連續記憶體給大物件的時候,讓FullGC觸發的時候進行老年代的整理工作。

    該引數通常和 -XX:CMSFullGCsBeforeCompaction 一起使用,-XX:CMSFullGCsBeforeCompaction 可以控制CMS發生多少次不整理的FullGC後,下一次FullGC進行老年代的整理。

七丶G1

Garbage First 簡稱G,它開創了收集器面向區域性收集,和基於Region的記憶體佈局。

1.停頓模型

停頓模型:支援在長度為M毫秒的時間內,消耗在垃圾回收上的時間大概不超過N毫秒的目標

G1如何實現這個目標:

摒棄了面向整個區域(整個新生代,老年代,堆)進行垃圾回收的樊籠。而是面向堆內任何部分來組成Collection Set(CSet)進行回收,而不是看它屬於哪個分代,而是看哪塊記憶體中存放的垃圾數量多,回收收益最大,這便是G1的Mixed GC。支撐整個目標實現的是G1基於Rigion的堆記憶體佈局

2.基於Region的堆記憶體佈局

可以看出G1也是遵循分代收集理論的,但是堆記憶體佈局不在堅持固定大小以及固定數量的分代區域劃分,而是把堆分為若干個Region,每一個Region可以是Eden,Survivor,Old中一種。其中還存在Humongous用於儲存大物件,如果一個物件超過Region大小的一半,那麼視為大物件,G1將這部分割區域視作老年代。

Region的大小可以通過-XX:G1HeapRegionSize進行設定,必須是2的冪次方,且介於1MB~32MB。超過一個Region大小的大物件講分配在N個連續的Humongous中。

G1每次回收都是回收若干個Region大小的空間,這也可以避免整堆收集,並且會使用一個優先順序列表來記錄和跟蹤每個Region中垃圾堆積的價值,價值即回收獲得的空間大小,和回收所需的時間的經驗值。每次垃圾回收都根據-XX:MaxGCPauseMillis(預設200ms)指定的停頓時間,選擇回收價值最大的Region。

3. G1 如何解決跨代參照垃圾回收的問題

Region裡面存在跨Region參照的問題依據是使用記憶集的思路去解決的。但是G1的記憶集更為複雜,每一個Region存在一個記憶集,這些記憶集會記錄其他Region指向自己的指標,並標記這些指標位於哪些卡頁的範圍內。

這種結構佔用更大的記憶體,因為如果堆記憶體太小不太適合使用G1垃圾收集器。

4.G1 如何解決並行標記階段,使用者執行緒更改參照關係造成的問題

G1使用原始快照的方式解決此問題

當灰色物件要刪除指向白色物件的參照關係時,就將刪除的參照記錄下來,並行掃描結束後,再將這些記錄過參照關係的灰色物件為根,重新掃描一次。

此外G1還設計了兩個指標稱為TAMS(Top at mark start)用於在並行回收階段,讓使用者執行緒在此區域分配記憶體給新物件。G1預設認為這個區域的物件是存活的。

5. G1 垃圾回收流程

  1. 初始標記

    標記了從GC Roots可以直接關聯可達的物件。需要Stop the world

  2. 並行標記

    和使用者執行緒並行執行,從GC Roots開始對堆中物件進行可達性分析,遞迴掃描整個堆裡的物件圖,找出要回收的物件、

  3. 最終標記

    需要Stop the world,處理並行標記階段,使用原始快照SATB記錄的物件。

  4. 篩選回收

    更新Region統計資訊,對各個Region的物件回收價值和成本進行排序,然後根據使用者期望的停止時間來指定回收計劃,然後選擇多個Region組成Collection Set(CSet)來進行回收,將回收這部分Region中存活的物件複製到空Region中,再清理整個舊Region的全部空間,這部分需要移動物件,依舊需要Stop the world,由多個執行緒並行完成。

6.G1 和CMS對比

6.1 G1優於CMS的點

  • 可以指定最大停頓時間
  • 可以按收益動態確定回收集
  • 篩選回收使用標記複製演演算法,不會產生記憶體碎片,可以避免無法分配連續記憶體而進行下一次收集的情況的發生

6.2 CMS 由於G1的點

  • CMS的記憶集只有一份,佔用記憶體小,但是G1每一個Region都有記憶集,所有G1佔用更多記憶體

    因此小記憶體機器上CMS更適合,堆記憶體足夠大的適合使用G1也是不錯的選擇。

  • CMS使用寫後屏障維護記憶集,並且是直接同步操作(更改參照的同時進行更新卡表)

    而G1為了實現原始快照,還需要使用寫前屏障,因此G1使用訊息佇列進行非同步處理 記憶集的更新