1. 程式人生 > >理解JVM之垃圾收集器詳解

理解JVM之垃圾收集器詳解

前言

垃圾收集器作為記憶體回收的具體表現,Java虛擬機器規範並未對垃圾收集器的實現做規定,因而不同版本的虛擬機器有很大區別,因而我們在這裡主要討論基於Sun HotSpot虛擬機器1.6版本Update22,此虛擬機器包含的收集器如下所示:

如圖展示了7種作用於不同分代的收集器,若兩個收集器之間存在連線,說明他們可以搭配使用。我們堆收集器進行比較就是為了針對具體的情況選擇最合適的收集器。

Serial收集器

Serial是最基本,最早的收集器,曾是JDK1.3.1之前的虛擬機器新生代唯一選擇,這個收集器是單執行緒收集器,它不僅僅只會使用一個單執行緒或一條收集執行緒區完成垃圾收集工作,更重要的是當進行垃圾收集時,其他工作執行緒必須暫停,直到收集結束。下圖為Serial/Serial Old收集器執行過程:

從JDK1.3到JDK1.7,HoSpot虛擬機器開發團隊一直在為消除或減少工作執行緒因記憶體回收而導致停頓的現象而努力著,從Serial收集器,到Parallel收集器,再到Concurrent Mark Sweep(CMS)使用者執行緒停頓時間不斷縮減,但此現象並未完全消除。
到目前為止,Serial是虛擬機器執行在Client模式下的預設的新生代收集器,它也有很多優點:簡單高效(與其他收集器單執行緒相比),對於限定單個CPU,由於沒有執行緒互動開銷,垃圾收集獲得最高的單執行緒收集效率。Serial收集器是執行在Client模式下的虛擬機器的好的選擇。

ParNew收集器

ParNew收集器是Serial收集器的多執行緒版本,除使用多執行緒進行垃圾收集外,其餘行為包括扣Serial收集器可用的控制引數、收集演算法、Stop The World 、物件分配規則、回收策略等與Serial完全一樣。ParNew收集器的工作過程如下圖所示:

ParNew是執行在Server模式下虛擬機種的首選新生代收集器,因為除了Serial收集器,目前只有它能夠與CMS收集器配合工作。ParNew收集器由於存線上程互動,可能不會有比Serial更好的效果但隨著CPU數量的增加,它對於GC時系統資源的利用有很大好處。
此處區分以下並行和併發:
並行(Parallel):多條垃圾收集執行緒並行工作,此時使用者執行緒仍處於等待狀態。
併發(Concurrent):指使用者執行緒與垃圾收集執行緒同時執行(不一定並行,可能交替執行),使用者程式繼續執行,垃圾收集程式執行在另一個CPU上。

Parallel Scavenge收集器

Parallel Scavenge收集器是一個新生代收集器,它時複製演算法的收集器,也是並行的多執行緒收集器,它的關注點與其他收集器不同,其它收集器儘可能縮短使用者執行緒停頓時間,Parallel Scavenge收集器則是達到一個可控制的吞吐量(吞吐量=執行使用者程式碼時間/(執行程式碼時間+垃圾收集時間))。停頓時間越短越適合需要使用者互動程式,良好相應速度能提升使用者體驗;高吞吐量則可以最高效率的利用CPU時間,儘快完成程式任務,適用於後臺運算而不需要太多互動的任務。

Serial Old收集器

Serial Old是Serial的老年版本,它也是單執行緒收集器,使用“標記-整理“演算法。它主要是被Client模式下的虛擬機器使用。在Server模式下,它有兩個用途:一是在JDK1.5及之前的本本種與Parallel Scavenge收集器搭配使用;二是作為CMS收集器的後備份預案,在併發收集發生Corrent Model Failure時候使用。Serial Old的工作過程如下圖所示:

Parallel Old收集器

Parallel Old使用多執行緒和“標記-整理”演算法,於JDK1.6開始提供,是Parallel Scavenge收集器的老年代版本。在其出現前,老年代除了Serial old外別無選擇,由於Serial old的“拖累”,使用Parallel Scavenge未必能夠在整體上實現吞吐量最大化的效果。Parallel Old出現後,“吞吐量”的黃金組合出現了,在注重吞吐量及CPU資源的敏感場合,優先考慮Parallel Old與Parallel Scavenge收集器組合。Parallel Old工作過程如圖 所示:

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。CMS收集器應用於那些重視服務響應速度,系統停頓短的場景。CMS應用了“標記-清除”演算法,他的運作過程包括四個場景:

  1. 初始標記(CMS initial mark)
  2. 併發標記(CMS concurrent mark)
  3. 重新標記(CMS ermark)
  4. 併發清除(CMS concurrent sweep)

由於在整個過程中耗時最長的併發標記與併發清除過程收集器執行緒可與使用者執行緒一起工作,因而,可看做併發標記與併發清除的收集器執行緒是與使用者執行緒一起併發執行的,其執行過程如下圖所示:

CMS是一款優秀的收集器,他的主要優點有:併發收集,低停頓,因而也稱為併發低停頓收集器,但其仍有缺點,主要表現在三個方面:

  1. CMS收集器對CPU資源非常敏感。面向併發設計的程式都對CPU比較敏感,在併發階段,它會佔用一部分執行緒導致應用程式變慢,降低總吞吐量。CMS預設自動回收執行緒數為(cpu數+3)/4,故當cpu總數小於4 時,幾乎要拿出50%的資源執行執行緒收集器。增量式併發收集器(i-CMS)就是為解決這種情況而創造的,他的思想是:併發標記和併發清理時,讓GC執行緒、使用者執行緒交替執行,儘量減少GC獨佔資源時間,雖然使得垃圾回收時間變長,但降低了對使用者程式的影響。目前,i-CMS已經不再提倡使用者使用。
  2. CMS收集器無法處理浮動垃圾。當出現“Concurrent Mode Failure”導致另一次Full GC的產生。此時,CMS併發清理階段使用者執行緒還在執行著,無法集中處理執行程式產生的垃圾。由於垃圾收集階段使用者執行緒還在執行,需給執行緒預留記憶體,要是預留的記憶體無法滿足需求,就會出現“Concurrent Mode Failure”失敗,此時,臨時啟用Serial Old進行老年代垃圾回收,耗費時間過長。
  3. CMS演算法是基於“標記-清除”的收集器,收集借書產生大量的空間碎片,當無法找到足夠的連續空間存放物件就會觸動Full GC。未解決這個問題,CMS增加一個碎片整理時間,此過程無法併發,停頓時間變長。

G1收集器

G1收集器與CMS相比有兩點改進:一是G1是基於“標記-整理”的收集器,不會產生空間碎片。二是他可以精確地控制停頓,能讓使用者指定在一個時間片段內消耗在垃圾收集上的時間。G1可以在不犧牲吞吐量的前提下完成低停頓 記憶體回收。G1將java堆劃分為多個大小獨立的區域,並根據區域的垃圾堆積程度,維護一個優先列表,每次根據收集時間,優先回收垃圾最多的區域,保證來G1收集器在有效時間內的的回收效率。

垃圾收集器引數彙總

引數 描述
UseSerialGC 虛擬機器執行在Client模式下的預設值,開啟此開關,使用Serial+Serial Old收集器組合進行記憶體回收
UseParNewGC 開啟此開關後,使用ParNew+Serial Old的收集器組合進行記憶體回收
UseConcMarkSweepGC 開啟此開關後,使用ParNew+CMS+Serial Old的收集器組合進行記憶體回收。Serial Old收集器將作為CMS收集器出現Concurrent Mode Failure失敗後的後備收集器使用
UseParallelGC 虛擬機器執行在Server模式下的預設值,開啟此開關後,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器組合進行記憶體回收
UseParallelOldGC 開啟此開關後,使用Parallel Scavenge + Parallel Old的收集器組合進行記憶體回收
SurvivorRatio 新生代中Eden區域與Survivor區域的容量比值,預設值為8,代表Eden:Survivor=8:1
PretenureSizeThreshold 直接晉升到老年代的物件大小,設定這個引數後,大於這個引數的物件將直接在老年代分配
MaxTenuringThreshold 晉升到老年代的物件年齡,每個物件在堅持過一次Minor GC之後,年齡就增加1,當超過這個引數時就進入老年代
UseAdaptiveSizePolicy 動態調整Java堆中各個區域的大小以及進入老年代的年齡
HandlePromotionFailure 是否允許分配擔保失敗,即老年代的剩餘空間不足以應付新生代的整個Eden和Survivor區的所有物件都存活的極端情況
ParallelGCThreads 設定並行GC時進行記憶體回收的執行緒數
GCTimeRatio GC時間佔總時間的比率,預設值為99,即允許1%的GC時間。僅在使用Parallel Scavenge收集器時生效
MaxGCPauseMillis 設定GC的最大停頓時間,僅在使用Parallel Scavenge收集器時生效
CMSInitingOccupancyFraction 設定CMS收集器在老年代空間被使用多少後觸發垃圾收集。預設值為68%,僅在使用CMS收集器時生效
UseCMSCompactAtFullCollection 設定CMS收集器在完成垃圾收集後是否要進行一次記憶體碎片整理,僅在使用CMS收集器時生效
CMSFullGCsBeforeCompaction 設定CMS收集器在進行若干次垃圾收集後再啟動一次記憶體碎片整理。僅在使用CMS收集器時生效

本文主要參考自《深入理解Java虛擬機器——JVM高階特性與最佳實踐》一書