JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

若小寒發表於2019-03-22

一、常見垃圾收集器

現在常見的垃圾收集器有如下幾種:

新生代收集器:

  • Serial
  • ParNew
  • Parallel Scavenge

老年代收集器:

  • Serial Old
  • CMS
  • Parallel Old

堆記憶體垃圾收集器:G1

每種垃圾收集器之間有連線,表示他們可以搭配使用。

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

二、新生代垃圾收集器

(1)Serial 收集器

Serial 是一款用於新生代的單執行緒收集器,採用複製演算法進行垃圾收集。Serial 進行垃圾收集時,不僅只用一條執行緒執行垃圾收集工作,它在收集的同時,所有的使用者執行緒必須暫停(Stop The World)。

就比如媽媽在家打掃衛生的時候,肯定不會邊打掃邊讓兒子往地上亂扔紙屑,否則一邊製造垃圾,一遍清理垃圾,這活啥時候也幹不完。

如下是 Serial 收集器和 Serial Old 收集器結合進行垃圾收集的示意圖,當使用者執行緒都執行到安全點時,所有執行緒暫停執行,Serial 收集器以單執行緒,採用複製演算法進行垃圾收集工作,收集完之後,使用者執行緒繼續開始執行。

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

適用場景:Client 模式(桌面應用);單核伺服器。

可以用 -XX:+UserSerialGC 來選擇 Serial 作為新生代收集器。

(2)ParNew 收集器

ParNew 就是一個 Serial 的多執行緒版本,其它與Serial並無區別。ParNew 在單核 CPU 環境並不會比 Serial 收集器達到更好的效果,它預設開啟的收集執行緒數和 CPU 數量一致,可以通過 -XX:ParallelGCThreads 來設定垃圾收集的執行緒數。

如下是 ParNew 收集器和 Serial Old 收集器結合進行垃圾收集的示意圖,當使用者執行緒都執行到安全點時,所有執行緒暫停執行,ParNew 收集器以多執行緒,採用複製演算法進行垃圾收集工作,收集完之後,使用者執行緒繼續開始執行。

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

適用場景:多核伺服器;與 CMS 收集器搭配使用。當使用 -XX:+UserConcMarkSweepGC 來選擇 CMS 作為老年代收集器時,新生代收集器預設就是 ParNew,也可以用 -XX:+UseParNewGC 來指定使用 ParNew 作為新生代收集器。

(3)Parallel Scavenge 收集器

Parallel Scavenge 也是一款用於新生代的多執行緒收集器,與 ParNew 的不同之處是ParNew 的目標是儘可能縮短垃圾收集時使用者執行緒的停頓時間,Parallel Scavenge 的目標是達到一個可控制的吞吐量。

吞吐量就是 CPU 執行使用者執行緒的的時間與 CPU 執行總時間的比值【吞吐量 = 執行使用者代程式碼時間/(執行使用者程式碼時間+垃圾收集時間)】,比如虛擬機器一共執行了 100 分鐘,其中垃圾收集花費了 1 分鐘,那吞吐量就是 99% 。比如下面兩個場景,垃圾收集器每 100 秒收集一次,每次停頓 10 秒,和垃圾收集器每 50 秒收集一次,每次停頓時間 7 秒,雖然後者每次停頓時間變短了,但是總體吞吐量變低了,CPU 總體利用率變低了。

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

可以通過 -XX:MaxGCPauseMillis 來設定收集器儘可能在多長時間內完成記憶體回收,可以通過 -XX:GCTimeRatio 來精確控制吞吐量。

如下是 Parallel 收集器和 Parallel Old 收集器結合進行垃圾收集的示意圖,在新生代,當使用者執行緒都執行到安全點時,所有執行緒暫停執行,ParNew 收集器以多執行緒,採用複製演算法進行垃圾收集工作,收集完之後,使用者執行緒繼續開始執行;在老年代,當使用者執行緒都執行到安全點時,所有執行緒暫停執行,Parallel Old 收集器以多執行緒,採用標記整理演算法進行垃圾收集工作。

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

適用場景:注重吞吐量,高效利用 CPU,需要高效運算且不需要太多互動。

可以使用 -XX:+UseParallelGC 來選擇 Parallel Scavenge 作為新生代收集器,jdk7、jdk8 預設使用 Parallel Scavenge 作為新生代收集器。

三、老年代垃圾收集器

(1)Serial Old 收集器

Serial Old 收集器是 Serial 的老年代版本,同樣是一個單執行緒收集器,採用標記-整理演算法。

如下圖是 Serial 收集器和 Serial Old 收集器結合進行垃圾收集的示意圖:

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

適用場景:Client 模式(桌面應用);單核伺服器;與 Parallel Scavenge 收集器搭配;作為 CMS 收集器的後備預案。

(2)CMS(Concurrent Mark Sweep) 收集器

CMS 收集器是一種以最短回收停頓時間為目標的收集器,以 “ 最短使用者執行緒停頓時間 ” 著稱。整個垃圾收集過程分為 4 個步驟:

① 初始標記:標記一下 GC Roots 能直接關聯到的物件,速度較快。

② 併發標記:進行 GC Roots Tracing,標記出全部的垃圾物件,耗時較長。

③ 重新標記:修正併發標記階段引使用者程式繼續執行而導致變化的物件的標記記錄,耗時較短。

④ 併發清除:用標記-清除演算法清除垃圾物件,耗時較長。

整個過程耗時最長的併發標記和併發清除都是和使用者執行緒一起工作,所以從總體上來說,CMS 收集器垃圾收集可以看做是和使用者執行緒併發執行的。

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

CMS 收集器也存在一些缺點:

對 CPU 資源敏感:預設分配的垃圾收集執行緒數為(CPU 數+3)/4,隨著 CPU 數量下降,佔用 CPU 資源越多,吞吐量越小

無法處理浮動垃圾:在併發清理階段,由於使用者執行緒還在執行,還會不斷產生新的垃圾,CMS 收集器無法在當次收集中清除這部分垃圾。同時由於在垃圾收集階段使用者執行緒也在併發執行,CMS 收集器不能像其他收集器那樣等老年代被填滿時再進行收集,需要預留一部分空間提供使用者執行緒執行使用。當 CMS 執行時,預留的記憶體空間無法滿足使用者執行緒的需要,就會出現 “ Concurrent Mode Failure ”的錯誤,這時將會啟動後備預案,臨時用 Serial Old 來重新進行老年代的垃圾收集。

因為 CMS 是基於標記-清除演算法,所以垃圾回收後會產生空間碎片,可以通過 -XX:UserCMSCompactAtFullCollection 開啟碎片整理(預設開啟),在 CMS 進行 Full GC 之前,會進行記憶體碎片的整理。還可以用 -XX:CMSFullGCsBeforeCompaction 設定執行多少次不壓縮(不進行碎片整理)的 Full GC 之後,跟著來一次帶壓縮(碎片整理)的 Full GC。

適用場景:重視伺服器響應速度,要求系統停頓時間最短。可以使用 -XX:+UserConMarkSweepGC 來選擇 CMS 作為老年代收集器。

(3)Parallel Old 收集器

Parallel Old 收集器是 Parallel Scavenge 的老年代版本,是一個多執行緒收集器,採用標記-整理演算法。可以與 Parallel Scavenge 收集器搭配,可以充分利用多核 CPU 的計算能力。

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

適用場景:與Parallel Scavenge 收集器搭配使用;注重吞吐量。jdk7、jdk8 預設使用該收集器作為老年代收集器,使用 -XX:+UseParallelOldGC 來指定使用 Paralle Old 收集器。

四、新生代和老年代垃圾收集器

G1 收集器

G1 收集器是 jdk1.7 才正式引用的商用收集器,現在已經成為 jdk9 預設的收集器。前面幾款收集器收集的範圍都是新生代或者老年代,G1 進行垃圾收集的範圍是整個堆記憶體,它採用 “ 化整為零 ” 的思路,把整個堆記憶體劃分為多個大小相等的獨立區域(Region),在 G1 收集器中還保留著新生代和老年代的概念,它們分別都是一部分 Region,如下圖:

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

每一個方塊就是一個區域,每個區域可能是 Eden、Survivor、老年代,每種區域的數量也不一定。JVM 啟動時會自動設定每個區域的大小(1M ~ 32M,必須是 2 的次冪),最多可以設定 2048 個區域(即支援的最大堆記憶體為 32M*2048 = 64G),假如設定 -Xmx8g -Xms8g,則每個區域大小為 8g/2048=4M。

為了在 GC Roots Tracing 的時候避免掃描全堆,在每個 Region 中,都有一個 Remembered Set 來實時記錄該區域內的引用型別資料與其他區域資料的引用關係(在前面的幾款分代收集中,新生代、老年代中也有一個 Remembered Set 來實時記錄與其他區域的引用關係),在標記時直接參考這些引用關係就可以知道這些物件是否應該被清除,而不用掃描全堆的資料。

G1 收集器可以 “ 建立可預測的停頓時間模型 ”,它維護了一個列表用於記錄每個 Region 回收的價值大小(回收後獲得的空間大小以及回收所需時間的經驗值),這樣可以保證 G1 收集器在有限的時間內可以獲得最大的回收效率。

如下圖所示,G1 收集器收集器收集過程有初始標記、併發標記、最終標記、篩選回收,和 CMS 收集器前幾步的收集過程很相似:

JVM面試問題系列:7種JVM垃圾收集器特點,優劣勢、及使用場景!

① 初始標記:標記出 GC Roots 直接關聯的物件,這個階段速度較快,需要停止使用者執行緒,單執行緒執行。

② 併發標記:從 GC Root 開始對堆中的物件進行可達新分析,找出存活物件,這個階段耗時較長,但可以和使用者執行緒併發執行。

③ 最終標記:修正在併發標記階段引使用者程式執行而產生變動的標記記錄。

④ 篩選回收:篩選回收階段會對各個 Region 的回收價值和成本進行排序,根據使用者所期望的 GC 停頓時間來指定回收計劃(用最少的時間來回收包含垃圾最多的區域,這就是 Garbage First 的由來——第一時間清理垃圾最多的區塊),這裡為了提高回收效率,並沒有採用和使用者執行緒併發執行的方式,而是停頓使用者執行緒。

適用場景:要求儘可能可控 GC 停頓時間;記憶體佔用較大的應用。可以用 -XX:+UseG1GC 使用 G1 收集器,jdk9 預設使用 G1 收集器。

五、JVM垃圾收集器總結

本文主要介紹了JVM中的垃圾回收器,主要包括序列回收器、並行回收器以及CMS回收器、G1回收器。他們各自都有優缺點,通常來說你需要根據你的業務,進行基於垃圾回收器的效能測試,然後再做選擇。下面給出配置回收器時,經常使用的引數:

-XX:+UseSerialGC:在新生代和老年代使用序列收集器

-XX:+UseParNewGC:在新生代使用並行收集器

-XX:+UseParallelGC :新生代使用並行回收收集器,更加關注吞吐量

-XX:+UseParallelOldGC:老年代使用並行回收收集器

-XX:ParallelGCThreads:設定用於垃圾回收的執行緒數

-XX:+UseConcMarkSweepGC:新生代使用並行收集器,老年代使用CMS+序列收集器

-XX:ParallelCMSThreads:設定CMS的執行緒數量

-XX:+UseG1GC:啟用G1垃圾回收器

JVM系列:

深入詳解JVM 記憶體區域及記憶體溢位分析

JVM的判斷物件是否已死和四種垃圾回收演算法

JVM 配置常用引數和常用 GC 調優策略

7種JVM垃圾收集器特點,優劣勢、及使用場景!

最後

後續會持續更新效能優化專題知識,寫的不好的地方也希望大牛能指點一下,大家覺得不錯可以點個贊在關注下,以後還會分享更多文章!


相關文章