7種JVM垃圾收集器特點,優劣勢、及使用場景——JVM系列(六)

寫在前面:2020年面試必備的Java後端進階面試題總結了一份複習指南在Github上,內容詳細,圖文並茂,有需要學習的朋友可以Star一下!
GitHub地址:https://github.com/abel-max/Java-Study-Note/tree/master

常見垃圾收集器

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

新生代收集器:

  • Serial
  • ParNew
  • Parallel Scavenge
    老年代收集器:
  • Serial Old
  • CMS
  • Parallel Old
    **堆內存垃圾收集器:**G1

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

image

新生代垃圾收集器

(1)Serial 收集器

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

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

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

image

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

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

(2)ParNew 收集器

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

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

image

**適用場景:**多核服務器;與 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 總體利用率變低了。

image

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

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

image

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

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

老年代垃圾收集器

(1)Serial Old 收集器

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

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

image

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

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

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

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

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

**③ 重新標記:**修正併發標記階段引用戶程序繼續運行而導致變化的對象的標記記錄,耗時較短。

**④ 併發清除:**用標記-清除算法清除垃圾對象,耗時較長。

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

image

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 的計算能力。

image

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

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

G1 收集器

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

image

每一個方塊就是一個區域,每個區域可能是 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 收集器前幾步的收集過程很相似:

image

**① 初始標記:**標記出 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垃圾回收器

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