JVM06-經典垃圾收集器

前言

上一篇我們介紹了JVM中幾種常見的垃圾收集算法。這一篇介紹下七種經典的垃圾收集器,如下圖所示:
在這裏插入圖片描述
上圖展示了7種作用於不同分代的垃圾收集器。如果兩個收集器之間存在連線,則說明它們可以搭配使用。圖中收集器所處的區域,則表示它是屬於新生代收集器抑或是老年代收集器。後面會對這7種垃圾收集器做詳細介紹,主要是從這些收集器的目標、特性、原理和使用場景進行介紹。

相關概念

並行和併發

  1. 並行(Parallel): 指多條垃圾收集線程並行工作,但此時用戶線程仍然處於等待狀態。
  2. 併發(Concurrent):指用戶線程與垃圾收集線程同時執行(但不一定是並行的,可能會交替執行),用戶程序在繼續運行。而垃圾收集程序運行在另一個CPU上。

吞吐量(Throughput)

吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即
吞吐量=運行用戶代碼的時間/(運行用戶代碼時間+垃圾收集時間)。
假設虛擬機總共運行了100分鐘,其中垃圾收集花掉了1分鐘,那吞吐量就是99%。

Minor GC和Full GC

  1. 新生代GC(Minor GC): 指發生在新生代的垃圾收集動作,因爲Java對象大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。
  2. 老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨着至少一次的Minor GC(但非絕對的,在Parallel Scavenge 收集器的收集策略裏就有直接進行Major GC的策略選擇過程)。Major GC的速度一般都會被Minor GC慢10倍以上。

新生代收集器

Serial收集器

Serial(串行)收集器是JDK1.3之前是虛擬機新生代收集的唯一選擇。。它是一個單線程工作的收集器,並且其進行垃圾收集時,用戶線程需要暫停,直到垃圾收集結束爲止(“Stop The World”)。這就好像你老媽打掃房間時,你需要離開房間一樣的道理。其採用的收集算法是標記-複製算法,如下圖就是Serial/Serial Old收集器搭配使用的運行示意圖。
在這裏插入圖片描述
Serial收集器的優點就是簡單高效,額外內存消耗最小。對於單核處理器或者處理器核數較少的環境來說,Serial收集器沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。所以,它依然是HotSpot虛擬機運行在Client模式下的默認的新生代收集器。

ParNew收集器

ParNew收集器實質上是Serial收集器的多線程並行版本,除了同時使用多條線程進行垃圾收集之外,其餘的行爲包括Serial收集器可用的所有控制參數(例如:-XX:SurvivorRatio)、收集算法、暫停用戶線程、對象分配規則、回收策略等都與Serial收集器完全一致。同樣的其採用的收集算法是標記-複製算法。下圖就是ParNew/Serial Old收集器搭配使用的運行示意圖。
在這裏插入圖片描述
ParNew收集器除了使用多線程收集外,其他方面與Serial收集器相比並無太多創新之處,但它卻是許多運行在Server模式下的虛擬機中首選的新生代收集器,其中一個重要的原因是,除了Serial收集器之外,目前只有它能和CMS收集器(Concurrent Mark Sweep)配合工作, CMS收集器後面會詳細介紹。
ParNew收集器在單CPU的環境中絕對不會有比Serial收集器有更好的效果,在多CPU環境下,隨着CPU的數量增加,它對於GC時系統資源的有效利用是很有好處的,它默認開啓的收集線程數與CPU的數量相同,可以通過-XX:ParallerGCThreads參數設置。

Parallel Scavenge收集器

Parallel Scavenge收集器也是一款新生代收集器,它同樣是基於標記-複製算法實現的收集器,也是能夠並行收集的多線程收集器,Parallel Scavenge的諸多特性從表面上看與ParNew非常相似,那它有什麼特別之處呢?
Parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是儘可能地縮短垃圾收集時用戶線程停頓的時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput)。吞吐量的說明前面有提到。
Parallel Scavenge收集器提供了兩個參數用於精確控制吞吐量,分別是控制最大 垃圾收集停頓時間的 -XX:MaxGCPauseMillis 參數以及直接設置吞吐量大小的 -XX:GCTimeRatio 參數。
-XX:MaxGCPauseMillis 參數允許的值是一個大於0的毫秒數,收集器將盡力保證內存回收花費的時間不超過用戶設定值。
-XX:GCTimeRatio參數的值則應當是一個大於0小於100的整數,也就是垃圾收集時間佔總時間的比率,相當於吞吐量的倒數。
由於與吞吐量關係密切,Parallel Scavenge收集器也經常被稱作"吞吐量優先收集器"。

老年代收集器

Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同樣是一個單線程收集器,使用標記-整理算法。這個收集器的主要意義也是供客戶端模式下的HotSpot虛擬機使用。如果在服務器模式下,它也可能有兩種用途, 一種是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用,另外一種就是作爲CMS收集器發生失敗時的後備預案。下圖就是Serial/Serial Old收集器搭配使用的運行示意圖。
在這裏插入圖片描述

Parallel Old收集器

Parallel Old收集器是Parallel Scavenage收集器的老年代版本,支持多線程併發收集,基於標記-整理算法實現。在注重吞吐量或者處理器資源較爲稀缺的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器這個組合。下圖就是Parallel Scavenge/Parallel Old收集器運行示意圖。
在這裏插入圖片描述

CMS收集器

CMS(Concurrent Mark Sweep)收集器是JDK1.5推出的一個具有劃時代意義的收集器,是一種以獲取最短回收停頓時間爲目標的收集器,它非常符合那些集中在互聯網站或者B/S系統的服務端上的Java應用,這些應用都非常重視服務的響應速度。從名字上看它是基於"標記-清除"算法實現的。
CMS收集器工作的這個流程分爲4個步驟:

  1. 初始標記(CMS initial mark) : 僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,需要"Stop The World"。
  2. 併發標記(CMS concurrent mark):進行GC Roots Tracing的過程,在整個過程耗時最長。
  3. 重新標記(CMS remark):爲了修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比並發標記的時間短,此階段也需要"Stop The World"。
  4. 併發清除(CMS concurrent sweep) :清理刪除掉標記階段判斷的已經死亡的對象,由於不需要移動存活的對象,所以這個階段也是可以與用戶線程同時併發的。
    由於在整個過程中耗時最長的併發標記和併發清除階段中,垃圾收集器線程都可以與用戶線程一起工作,所以從總體上來說,CMS收集器的內存回收過程就是與用戶線程一起併發執行的。通過下圖,可以比較清楚地看到CMS收集器的運作步驟中併發和需要停頓的階段。
    在這裏插入圖片描述

優點

CMS是一款優秀的收集器,它最主要的優點就是:併發收集、低停頓,因此CMS收集器也被稱爲併發低停頓收集器(Concurrent Low Pause Collector)

缺點

  1. 對CPU資源非常敏感,在併發階段,它雖然不會導致應用程序變慢,但是總吞吐量會降低。CMS默認啓動的回收線程數是(CPU數量+3)/4,也就是當CPU在4個以上時,併發回收時垃圾收集線程不少於25%的CPU資源,並且隨着CPU數量的增加而下降,但是當CPU不足4個時(比如2個),CMS對用戶程序的影響就可能變得很大, 如果本來CPU負載就比較大,還要分出一半的運算能力去執行收集器線程,就可能導致用戶程序的執行速度忽然降低50%,其實也讓人無法接受。
  2. 無法處理浮動垃圾,可能出現"Concurrent Mode Failure"失敗而導致另一次Full GC的產生,由於CMS併發清理階段用戶線程還在運行着,伴隨着程序運行自然就會有新的垃圾不斷產生。 這一部分垃圾出現在標記過程之後,CMS無法再當次收集中處理掉它們,只好留待下一次GC時再清理掉。這一部分垃圾就被稱爲“浮動垃圾”。
  3. 標記-清除算法導致的空間碎片 CMS收集器是基於"標記-清除"算法實現的收集器,這意味着每次收集結束之後會有大量空間碎片產生。

G1收集器

G1(Garbage-First)收集器是當今收集器技術發展最前沿的成果之一,他是一款面向服務端應用的垃圾收集器,HotSpot開發團隊賦予它的使命是未來可以替換掉JDK1.5中發佈的CMS收集器。與其他GC收集器相比,G1具備如下特點:

  1. 並行與併發:G1能充分利用多CPU、多核環境下的硬件優勢,使用多個CPU來縮短"Stop The World"停頓時間,部分其他收集器原本需要停頓Java線程執行的GC動作,G1收集器仍然可以通過併發的方式讓Java程序繼續執行。
  2. 分代收集與其他收集器一樣,分代概念在G1中仍然得以保留,雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但它能夠採用不同方式去處理新創建的對象和已存活一段時間、熬過多次GC的舊對象來獲取更好的收集效果。
  3. 空間整合 G1從整體來看是基於"標記-整理"算法實現的收集器,從局部(兩個Region之間)上來看是基於"複製"算法實現的。這意味着G1運作期間不會產生內存碎片,垃圾收集完成之後能夠提供規整的可用內存。此特性有利於程序長時間運行,分配大對象時不會因爲無法找到連續內存空間而提前觸發下一次GC。
  4. 可預測的停頓:這是G1相對CMS的一大優勢,降低停頓時間是G1和CMS共同的關注點,但G1除了降低停頓外,還能建立可預測的停頓時間模型。能讓使用者明確指定在一個長度爲M毫秒的時間片段內,消耗在GC上的時間不得超過N毫秒,這幾乎是實時Java(RTSJ)的垃圾收集器的特徵了。

橫跨整個堆內存

在G1之前的其他收集器進行收集的範圍都是整個新生代或者老年代,而G1不再是這樣的,G1在使用時,Java堆的內存佈局與其他收集器有很大區別,它將整個Java堆劃分爲多個大小相等的獨立區域(Region), 雖然還保留新生代和老年代的概念,但新生代和老年代不再是物理隔離的,而都是一部分Region(不需要連續)的集合

建立可預測的時間模型

G1收集器之所以能建立可預測的停頓時間模型,是因爲它可以有計劃地避免在整個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需要時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的來由)。這種使用Region劃分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內可以獲取儘可能高的收集效率。

避免全堆掃描-Remembered Set

G1把Java堆分爲多個Region,就是"化整爲零"。但是Region不可能是孤立的,一個對象分配在某個Region中,可以與整個Java堆任意的對象發生引用關係。在做可達性分析缺點對象是否存活的時候,需要掃描整個Java堆才能保證準確性,這顯然是對GC效率的極大傷害。
爲了避免全堆掃描的發生。虛擬機爲G1中每個Region維護了一個與之對應的RememberedSet。虛擬機發現程序在對Reference類型的數據進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用的對象是否處於不同的Region之中(在分代的例子中就是檢查是否老年代中的對象引用了新生代中的對象),如果是,便通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set之中。當進行內存回收時,在GC根節點的枚舉範圍內加入Remembered Set即可保證不對全堆掃描也不會有遺漏。

如果不計算維護Remembered Set的操作,G1收集器的運作大致可劃分爲以下幾個步驟 :

  1. 初始標記(Initial Marking) 僅僅只是標記一下GC Roots能直接關聯到的對象,並且修改TAMS(Nest Top Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確可以的Region中創建對象,此階段需要停頓線程,但耗時很短。
  2. 併發標記(Concurrent Marking) 從GC Root 開始對堆中對象進行可達性分析,找到存活對象,此階段耗時較長,但可與用戶程序併發執行
  3. 最終標記(Final Marking) 爲了修正在併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程的Remembered Set Logs 裏面,最終標記階段需要把Remembered Set Logs的數據合併到Remembered Set中, 這階段需要停頓線程,但是可並行執行。
  4. 篩選回收(Live Data Counting and Evacuation) 首先對各個Region中的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計劃,可以自由選擇任意多個Region構成回收集,然後把決定回收的那一部分的Region的存活對象複製到空的Region中。再清理掉整個舊Region的全部空間。這裏的操作涉及存活對象的移動,是必須暫停用戶線程,由多條收集器線程並行完成的。
    下圖比較清楚的展示了G1收集器的運作步驟。
    在這裏插入圖片描述

總結

本文主要介紹了JVM七種經典的收集器做完了詳細的說明。之所以要有這麼多收集器,是因爲沒有一種收集器是完美的。在JDK1.5 推出來具有劃時代意義的CMS收集器,它的特點就是併發收集,低停頓。而JDK1.7推出了收集器的集大成者—G1收集器。它的特點就是併發收集,可預測的停頓,不會產生碎片化

收集器 串行、並行or併發 新生代/老年代 算法 目標 適用場景
Serial 串行 新生代 標記-複製算法 響應速度優先 單CPU環境下Client模式
ParNew 並行 新生代 標記-複製算法 響應速度優先 多CPU環境時在Server模式下與CMS配合
Parallel Scavenge 並行 新生代 標記-複製算法 吞吐量優先 在後臺運算不需要太多交互的任務
Serial Old 串行 老年代 標記-整理算法 響應速度優先 單CPU環境下的Client模式、CMS的後備預案
Parallel Old 並行 老年代 標記-整理算法 吞吐量優先 在後臺運算而不需要太多交互的任務
CMS 並行 老年代 標記-清除 響應速度優先 集中在互聯網站或B/S系統服務端上的Java應用
G1 並行 所有 標記-整理+標記-複製算法 響應速度優先 面向服務端應用,將來替換CMS

參考資料

深入理解JVM(3)——7種垃圾收集器
深入理解Java虛擬機(第3版)

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