到底是誰在回收 JVM 的垃圾

lvgo發表於2021-01-01

到底是誰在回收 JVM 的垃圾

JVM07

虛擬機器的垃圾回收器,沒有哪一個是絕對好的,只有比較好的。

今天的這篇文章,我要與你分享虛擬機器的那些垃圾回收器們。內容不多,可以耐心看完。

垃圾收集器

之前我們已經瞭解過具體的 GC 涉及的細節知識點,現在我們站在更高的角度,來看看各種垃圾收集器,以及其中兩個的工作過程(CMS 和 G1)。

我們知道,虛擬機器將儲存物件例項的區域分為了兩個叫做 新生代老年代 的地方,為此虛擬機器針對不同的記憶體區域利用不同的演算法設計了不同的垃圾收集器。

瞭解垃圾收集器之前,我覺得還是有必要在複習一下 Stop The World ,其用來形容在安全點使用者執行緒暫停的這種狀態的一個叫法。關於這個垃圾收集器工作的時候為什麼要 Stop The World 還有一個比較有意思的事 ,“你媽媽在給你打掃房間的時候, 肯定也會讓你老老實實地在椅子上或者房間外待著, 如果她一邊打掃, 你一邊亂扔紙屑, 這房間還能打掃完? ”這是虛擬機器團隊對 Stop The World 的說明,嗯,很有道理,哈哈哈。

那些年的垃圾收集器

Serial

關鍵字:新生代、Stop The World 、標記 - 複製演算法、單執行緒

Serial 是作用在新生代的垃圾收集器,單執行緒工作,在工作的時候需要 Stop The World,(包括之前提到的 GC 工作,都是指可達性分析)。採用的是標記 - 複製演算法,(關於標記 - 複製的內容之前有講過就不再展開說了)。

看起來 Serial 收集器沒什麼特別的,但實際上 Serial 收集器在一些特殊的場景下有著不錯的表現,這些要得益於他的額外記憶體消耗,因為其相比其他收集器要小一些,所以在伺服器資源受限的情況下(單核或較少核心以及記憶體緊張),這個簡單的單執行緒收集器效率還是很可觀的。

ParNew

關鍵字:新生代、Stop The World 、標記 - 複製演算法、多執行緒、絕配

ParNew 可以看成 new parallel gc ,一個新的並行垃圾收集器。是一個對標 Serial 的收集器,它與 Serial 的區別就是它在工作的時候是使用多執行緒進行工作的。還有,它是目前(JDK 9 以後)唯一一個能配合 CMS 在新生代工作的垃圾收集器。

Parallel Scavenge

關鍵字:新生代、Stop The World、標記 - 複製演算法、多執行緒、可控吞吐量【使用者執行緒執行時間 /(使用者執行緒執行時間+GC執行緒執行時間)】、自適應策略

與 ParNew 相比,Parallel Scavenge 多了一些額外的功能,停頓時間和吞吐量可控(通過引數配置-XX: MaxGCPauseMillis 單位毫秒,控制每次垃圾回收的最大停頓時間。-XX: GCTimeRatio 控制吞吐量 0 - 100 整數,1/(1+設定的引數) = 垃圾收集時間佔程式執行的總時間比率。

對於 Parallel Scavenge 還有一個特點,就是自適應策略,把記憶體管理工作完全交由虛擬機器,通過引數 -XX: +UseAdaptiveSizePolicy 啟用自適應記憶體策略,這樣就不需要指定記憶體引數,比如新生代大小、eden survivor 比例 晉升年齡等引數。虛擬機器會根據當前系統執行狀態動態的調整,達到一個合適的停頓時間和吞吐量。

上面這三種 SerialParNewParallel Scavenge 都是新生代的垃圾收集器,下面我們來看看老年代的垃圾收集器。

Serial Old

關鍵字:老年代、Stop The World、標記 - 整理演算法、單執行緒、替補

Serial Old 是和 Serial 一樣的一款收集器,只不過,它是工作在老年代的。換句話說就是 Serial Old 就是 Serial 的老年代版本。Serial Old 有一個特殊的用途就是作為 CMS 併發清除失敗的時候的替補,這裡後面 CMS 收集器再看。

Parallel Old

關鍵字:老年代、Stop The World、標記 - 整理演算法、多執行緒、CP(組合)

Parallel Old 是一個多執行緒並行的老年代垃圾收集器。它的出現也是為了解決吞吐量最大化的問題。因為他沒出現之前,只有 Serial Old 一款老年代垃圾收集器來配合 Parallel Scavenge ,因為 Serial Old 的單執行緒效能原因,導致 Parallel Scavenge 吞吐量的優勢體現不出來。直到它的出現,它與 Paralle Scavenge 就組成了一對完美的吞吐量 CP。

CMS

關鍵字:老年代、Stop The World、標記 - 清除演算法、多執行緒、短停頓

Concurrent Mark Sweep 的設計初衷就是要停頓的時間最短!JDK 5 開始使用,JDK 9 以前最優秀,為啥是 JDK 9 以前最優秀,因為 JDK 9 釋出的時候,預設啟用了 G1 收集器,同時你如果手動改成 CMS 的話,會受到一個 CMS 被宣告為不推薦的警告,下面是它具體的工作過程,一共經歷 4 個階段

具體步驟如下:

  • 初始標記:需要 Stop The World
    • 標記 GC Roots 直接關聯的物件(直達),速度較快,停頓時間短。
  • 併發標記:與使用者執行緒併發
    • 進行標記追蹤,完成全部物件的標記任務。可能出現漏標或錯標情況。
  • 重新標記:需要 Stop The World
    • 修正併發標記階段的物件標記,因為大部分物件不需要修正,所以執行時間相比並發標記時間短,但是停頓時間要比初始標記停頓時間長。
  • 併發清除:與使用者執行緒併發
    • 耗時較長,可以與使用者執行緒共同工作。

CMS 雖然有了一個較短的停頓時間,但是也有一些其他隨之而來的問題。

優點:

  • 併發執行速度快、停頓時間短。這一點沒得說,因為它其中有兩個階段是和使用者執行緒併發。

缺點:

  • 佔用執行緒資源,因為 CMS 工作有兩個階段是和使用者執行緒併發,所以這裡便會搶佔使用者執行緒資源。
  • 浮動垃圾,清理一次之後還會有清理不掉的物件,需要在下次清理的時候才能夠清理到。這裡的原因是因為併發清除階段是和使用者執行緒併發,一邊清除一邊使用,可能會出現一些無法清理掉的新生垃圾,比如清理過程中,程式斷開了某個引用,被斷開的引用 GC Roots 不可達,所以這個被斷開的引用指向的物件變成了浮動垃圾。
  • 空間利用率低,因為併發清理的原因,所以不能等到記憶體完全用完之後再做清理,所以需要當記憶體使用達到一定閾值(預設值68%,JDK6的時候提高到了 92%)時就開始進行垃圾回收動作,具體數值可以通過引數控制。這裡 JVM 給了風險預案:凍結使用者執行緒,啟動 serial old 來進行一次老年代垃圾回收。這也是上面我們說 Serial Old 的時候,提到他的關鍵字裡有 “替補” 的原因。
  • 空間碎片,因為使用標記-清除演算法的原因,會導致碎片空間的產生。CMS 的做法是在其不能夠滿足物件分配任務的時候,FULL GC 的時候,會進行一次空間整理的動作。對這個整理的動作也是有引數可以進行控制,引數設定情況為,滿足幾次FullGC之後,進行一次空間整理,預設值為 0 ,即每次 full gc 都會進行一次空間整理。這一點雖然是缺點,不過 CMS 已經盡力去你補了,包括這裡的 FULL GC 之後的記憶體空間整理,還有物件分配時 CMS 會在 Free List 申請一塊較大的記憶體空間,然後通過指標碰撞的方式來進行物件分配,儘可能減小空間碎片的產生。空間碎片問題也是 CMS 不能直接使用指標碰撞的方式來為物件分配記憶體的原因。

Garbage First (G1)

關鍵字:里程碑、JDK 9、區域管理、按需回收、延遲可控的最高吞吐量

要說 CMS 是一個劃時代收集器,那 G1 可以稱得上劃時代的劃時代收集器,其作為 JVM 的垃圾收集器的里程碑是有一定原因的,我們繼續往下看。G1 出現的原因也很簡單,那就是替換掉 CMS 。G1的設計是顛覆性的設計思路,它跳出了記憶體一定要劃分新生代老年代的這個枷鎖,它的工作模式為 Mixed Mode。並且 JDK 9 的時候開始啟用,成為了服務端模式下的預設垃圾收集器,替換掉了原來的吞吐量組合(Parallel Scavenge + Parallel Old),同時 CMS 被宣告為不推薦使用,CMS 也是從JDK 9 開始準備退役。按需回收說的是 G1 在做清理的時候,是依據一個可預測停頓時間模型來做的,這是個什麼東西呢?簡單來說就是,在清理之前,G1 對每個待回收的區域根據回收價值和時間進行排序,然後根據使用者所期望的停頓時間來做一個最優回收,後面會繼續說。

多瞭解一點,關於 G1 的工作模式,Mixed Mode 的擴充套件:G1 有純 GC 模式和分代回收模式,分代模式會分為 Minor GC 和 Mixed GC 兩種,這裡的模式選擇會影響最後的篩選回收階段的回收集合的內容。這塊內容可參考後面留的 R大 的連結

上面有一點展開說一下,就是 G1 不是沒有這分代這種操作了嗎?是通過記憶體區域來管理垃圾的,但是事實上 G1 將記憶體分成多個大小相等的 Region(區域) ,這些 Region 都可以作為Eden、Survivor、Humongous(Humongous 同老年代的作用)。

下面我們一起了解一下 G1 工作的具體步驟:

  • 初始標記:Stop The World

    • 標記 GC Roots 直接關聯的物件,同時修改 TAMS 指標
  • 併發標記:

    • 標記全部要回收的物件,與使用者執行緒併發,標記完成之後,重新處理 SATB 記錄下在併發時有引用變動的物件
  • 最終標記:Stop The World

    • 處理併發標記階段 SATB 遺留的引用,同時這個階段也進行弱引用處理。
  • 篩選回收:Stop The World

    • 這個階段會更新 Region 的統計資料,對每個 Region 根據其回收價值和成本進行排序,然後根據使用者所期望的停頓時間(引數設定)來制定一個回收計劃。再根據這個計劃,選擇任意 Region 來組成一個回收集(collection set)。將回收集中的 Region(被選中的區域) 中的存活物件複製到空的 Region 中,然後將舊的(選中的) Region 清理掉。

      以上過程由多執行緒並行完成,同時因為移動物件需要暫停使用者執行緒(Stop The World)

TAMS:Top at Mark Start Region 中的兩個指標名稱,他們的作用是將 Region 的一部分空間劃分出來給併發回收過程中程式執行產生的新物件使用

SATB:原始快照,還記得之前我們對漏標的解決方案嗎?一種是增量更新(CMS 採用的這種方案),另外一個就是原始快照,這裡可以翻翻之前的內容。

G1除了併發標記階段都需要暫停使用者執行緒

G1的理想目標:在延遲可控的情況下達到最大的吞吐量

使用者可以通過引數設定所期望的停頓時間,這個時間一般建議設定為 100 ~ 300 ms。

垃圾收集器小結

上面一共說了 7 款垃圾收集器,不過他們的具體使用我覺得有必要了解一下。

按照年代劃分

新生代:Serial 、ParNew、Parallel Scavenge

老年代:Serial Old、Parallel Old、CMS

記憶體區域:G1

搭配組合

因為不同的階段,垃圾收集器之間的搭配不同,所以我們就按照 JDK 9 作為劃分界線,來看下 JDK 9 前後的搭配情況

JDK 9 以前

JVM-JDK9之前

JDK 9 之後

JVM-JDK9以後

通過搭配關係我們可以看出,JDK 9 以前,Hotspot 提供了多種選擇,而且場面看起來很和諧,解釋一下 CMS 與 Serial Old 之間的虛線,這代表 CMS 併發清除失敗的時候,以 Serial Old 作為備選方案的組合。

JDK 9 之後,因為 G1 的出現,hotspot 取消了兩種方案的支援(Serial + CMS 和 ParNew + CMS),僅提供了 4 種虛擬機器搭配方案,他們分別是

  • Serial + Serial Old 的單執行緒組合,適用於資源受限的場景。
  • ParNew + CMS 這組曾經的王者組合,新生代的多執行緒並行高效能加上老年代的短暫停頓組合,可以應對大部分場景。
  • Parallel Scavenge + Serial Old 這是 Paralle Old 沒出現的時候的應對組合(不明白為何 JDK 9 的時候沒取消這對奇葩組合)
  • Parallel Scavenge + Parallel Old 這對高吞吐量 CP

看起來這 4 組搭配很完美,不過因為 G1 的出現,看起來的美好也沒那麼好了 G1在 server 模式下取代了高吞吐量的 CP (Parallel Scavenge + Parallel Old)成為了預設的垃圾收集器,同時在 JDK 9 使用 ParNew + CMS 這組搭配時,還會收到來自 hotspot 的警告,CMS 已經被宣告為不推薦使用,因為 ParNew 此時只能與 CMS 搭配使用,所以可以說當時 CMS 拯救了 ParNew 的尷尬局面(當時新生代高效能的 ParNew 只能選擇拖後腿的 Serial Old 一起工作),現在 ParNew 也要陪著 CMS 一起下崗了。綜上所述,JDK 9 還剩下建議使用的組合

  • G1
  • Serial + Serial Old
  • Parallel Scavenge + Serial Old (還是它,我一定要關注它倆啥時候下崗)

所以通過上面的分析,也能看出來 HotSpot 的用意,在 JDK 9 以後就是要將 G1 作為一個全能型的垃圾收集器來發展。

寫在最後

上面總結了截止 JDK 9 的垃圾收集器內容,其實對於垃圾收集器還有很多內容,比如 Shenandoah ,一個由 Red Hat 開發的低延遲垃圾收集器,還有 Oracle 後面的 ZGC。這兩個垃圾收集器都採用了更加優秀的思想和實現方案。不過因為我沒有對其深入的瞭解,所以在這就不再多說了。如果你對垃圾收集器的具體演算法仍感興趣,推薦訪問下面這個連結,R大 寫的虛擬機器相關內容

https://www.iteye.com/blog/rednaxelafx-362738

相關文章