天天看點

Hotspot垃圾收集器大全

上篇文章我們介紹了如何判斷對象是否為垃圾,有垃圾就要清理,清理就需要 垃圾收集器。

垃圾收集器(Garbage Collector)是垃圾收集GC的實作,根據是否分代收集可将垃圾收集器分為兩種類型:分代收集和不分代收集。

Hotspot垃圾收集器大全

垃圾收集器概覽

分代收集

Serial垃圾收集器

Serial垃圾收集器是最簡單的垃圾回收器的實作,同時它也是最古老的垃圾回收器,在jdk 1.3之前是HotSpot虛拟機新生代收集器的唯一選擇。

Serial翻譯成中文的意思是“串行的”,顧名思義Serial垃圾收集器就是一個單線程的垃圾回收器,它的這個串行指的是當它要進行垃圾回收時,其他所有的工作線程必須都要暫停,直到它回收結束,也就是我們常說的STW(Stop The World)。這很影響使用者體驗,如果每運作1個小時,暫停5分鐘,使用者就要等5分鐘,别說5分鐘了,哪怕是半分鐘不響應,也會讓人覺得這個網站真的很垃圾。在多線程應用程式中它不是一個好的選擇,比如在伺服器端使用,但是對于運作在用戶端模式下的虛拟機來說是一個很好的選擇,它也是HotSpot虛拟機運作在用戶端模式下的預設新生代收集器。Serial垃圾收集器隻适合幾十兆到一兩百兆的堆空間進行垃圾回收,在這種小記憶體中,它可以控制停頓時間在100ms以内,是可以被使用者接受的。

Serial Old收集器用于老年代的回收,一般搭配着Serial收集器使用。Serial Old收集器的主要意義也是供用戶端模式下的HotSpot虛拟機使用。如果在服務端模式下,它也可能有兩種用途:一種是在JDK 5以及之前的版本中與Parallel Scavenge收集器搭配使用,另外一種就是作為CMS收集器發生失敗時的後備預案。它和Serial收集器設計思想是一樣的,隻是回收的區域不同而已。

Hotspot垃圾收集器大全

Serial/Serial Old收集器運作示意圖

「特點」:

  • 古老
  • 簡單高效(和其他單線程垃圾回收器比較)
  • 單線程串行
  • 适合在用戶端模式下和單CPU環境中使用

ParNew垃圾收集器

「ParNew」是「Parallel New」的縮寫,從名字可以看出,它是并行并且是收集新生代的垃圾回收器。ParNew垃圾收集器是Serial垃圾收集器的多線程并行版本,除了「并行收集」以外,其他的實作基本和Serial垃圾收集器無異。

在JDK 7以前ParNew垃圾收集器是新生代垃圾回收的首選,它主要是配合CMS垃圾收集器使用。不過JDK 9開始将ParNew垃圾收集器合并到了CMS垃圾收集器中。

Hotspot垃圾收集器大全

多線程收集器運作示意圖

Parallel 垃圾收集器

Parallel垃圾收集器是JDK 8預設的垃圾收集器,它也是一個并行多線程的垃圾收集器,跟CMS垃圾收集器類似,隻不過CMS垃圾收集器側重點是減少垃圾收集時使用者線程停頓的時間,而Parallel垃圾收集器的側重點是控制吞吐量以達到高效率地利用 CPU時間,盡快完成應用程式的運算任務。是以Parallel垃圾收集器有時也稱為「吞吐量優先的收集器」(Throughput Collector)。吞吐量是根據進行垃圾收集所花費的時間與在垃圾收集之外所花費的時間(稱為應用程式時間)進行計算的。

吞吐量=應用程式時間/(應用程式時間+垃圾收集時間)           

Parallel垃圾收集器分為Parallel Scavenge和Parallel Old垃圾收集器。Parallel Scavenge垃圾收集器負責新生代的垃圾收集,Parallel Old垃圾收集器負責老年代的垃圾收集。

可以通過以下參數控制Parallel 垃圾收集器的行為:

  • -XX:MaxGCPauseMillis=<t>:控制最大GC停頓時間,機關毫秒,預設情況下沒有值。當設定停頓時間時,JVM會調整堆大小和其他跟垃圾回收相關的參數盡可能的小于設定的這個停頓時間。不過不要想着把MaxGCPauseMillis設定的越小越好,MaxGCPauseMillis設定小了,頻率就變快了。比如,堆記憶體是500M,垃圾收集時間是10s,停頓時間是100ms,如果把MaxGCPauseMillis設定為70ms,為了達到70ms,堆記憶體就減小變成了300M,垃圾收集時間變成了5s,單次的停頓時間是變短了,整體的效率變低了,之前每隔10s停頓100ms,現在每隔10s停頓140ms,犧牲了吞吐量。而且MaxGCPauseMillis設定了之後不一定就能達到預期,并且如果對垃圾收集器運作原理不了解的話,設不準很容易出現問題。
  • -XX:GCTimeRatio=<N>:設定吞吐量大小。GCTimeRati就是設定垃圾收集時間與應用程式時間之比的,公式為1 / (1 + <N>)。比如:-XX:GCTimeRatio=19,這意味着垃圾回收時間占總時間的1 / (1 + 19)= 1/20,也就是5% ,GCTimeRatio預設值為99,即允許最大的垃圾回收時間為**1%**。
  • -Xmx<N>:設定堆記憶體大小。收集器的隐式目标是在滿足其他參數的情況下最小化堆的大小。

垃圾收集器會先滿足參數-XX:MaxGCPauseMillis,然後再滿足參數-XX:GCTimeRatio,在這兩個參數都沒滿足的情況下參數才會被考慮-Xmx<N>。

-XX:GCTimeRatio=<N>和-XX:GCTimeRatio=<N>參數尤其不好控制,是以在Parallel 垃圾收集器中提供了另一個參數-XX:+UseAdaptiveSizePolicy,它是預設開啟的,開啟之後虛拟機會根據目前系統的運作情況收集性能監控資訊,動态調整這些參數以提供最合适的停頓時間或者最大的吞吐量。

CMS垃圾收集器

CMS(Concurrent Mark Sweep)收集器是一種以擷取最短回收停頓時間為目标的收集器,它負責老年代的垃圾回收,新生代隻能選擇 ParNew或者Seria收集器中的一個。CMS收集器在JDK 5釋出,在JDk 9中被标記過時,在JDK 14中已經被删除,不過這并不影響它是一款具有劃時代意義的垃圾收集器,它是HotSpot虛拟機中第一款真正意義上支援并發的垃圾收集器,它首次實作了讓垃圾收集線程與使用者線程(基本上)同時工作。CMS 收集器也被稱為低延遲垃圾收集器。

從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于标記-清除算法實作的,也是唯一一款基于标記-清除算法實作的老年代收集器,它的運作過程相對于前面幾種收集器來說要更複雜一些,整個過程分為四個步驟,包括:

  1. 「初始标記(CMS initial mark)」:僅僅隻是标記一下GC Roots能直接關聯到的對象,速度很快。
  2. 「并發标記(CMS concurrent mark)」:從GC Roots的直接關聯對象開始周遊整個對象圖的過程,這個過程耗時較長但是不需要停頓使用者線程,可以與垃圾收集線程一起并發運作。
  3. 「重新标記(CMS remark)」:為了修正并發标記期間,因使用者程式繼續運作而導緻标記産生變動的那一部分對象的标記記錄,這個階段的停頓時間通常會比初始标記階段稍長一些,但也遠比并發标記階段的時間短。
  4. 「并發清除(CMS concurrent sweep)」:清理删除掉标記階段判斷的已經死亡的對象,由于不需要移動存活對象,是以這個階段也是可以與使用者線程同時并發的。

其中初始标記、重新标記這兩個步驟仍然需要“Stop The World”。由于整個過程中耗時最長的并發标記和并發清除過程收集器線程都可以與使用者線程一起工作,是以,從總體上來說,CMS 收集器的記憶體回收過程是與使用者線程一起并發執行的。

Hotspot垃圾收集器大全

CMS垃圾收集器運作示意圖

CMS垃圾收集器有以下缺陷:

  1. 「CPU 敏感」: CMS對處理器資源敏感,畢竟采用了并發的收集、當處理核心數不足4個時,CMS對使用者的影響較大。
  2. 「浮動垃圾」:由于CMS并發清理階段使用者線程還在運作着,伴随程式運作自然就還會有新的垃圾不斷産生,這一部分垃圾出現在标記過程之後,CMS無法在當次收集中處理掉它們,隻好留待下一次GC時再清理掉。這一部分垃圾就稱為“「浮動垃圾」” 。 由于浮動垃圾的存在,是以需要預留出一部分記憶體,意味着CMS收集不能像其它收集器那樣等待老年代快滿的時候再回收。如果預留的記憶體不夠存放浮動垃圾,就會出現「Concurrent Mode Failure」,這時虛拟機将臨時啟用Serial Old收集器來替代CMS收集器,這樣停頓的時間就更長了。
  3. 「會産生空間碎片」:由于CMS是一款基于“标記-清除”算法實作的收集器,是以肯定會産生不連續的空間碎片。

不分代收集

Garbage First收集器

Garbage First(簡稱G1)收集器是垃圾收集器技術發展曆史上的裡程碑式的成果,它開創了收集器面向局部收集的設計思路和基于Region的記憶體布局形式。

G1也遵循分代收集理論,但是它不像上面分代收集垃圾收集器那樣實體分代,而是采用Region的概念邏輯分代,實體分區:每個Region可以扮演新生代的Eden空間、Survivor空間,或者老年代空間收集器能夠對扮演不同角色的Region采用不同的政策去處理,這樣無論是新建立的對象還是已經存活了一段時間、 熬過多次收集的舊對象都能擷取很好的收集效果。

Hotspot垃圾收集器大全

Region示意圖

G1收集器去跟蹤各個Region裡面的垃圾堆積的“價值”大小,價值即回收所獲得的空間大小以及回收所需時間的經驗值, 然後在背景維護一個優先級清單,每次根據使用者設定允許的收集停頓時間(使用參數-XX:MaxGCPauseMillis指定,預設值是200毫秒),優先處理回收價值收益最大的那些Region,這也就是“Garbage First”名字的由來。

G1運作過程包括4個步驟:初始标記、并發标記、最終标記和篩選回收,除了并發标記外,其餘階段也是要完全暫停使用者線程的。

Hotspot垃圾收集器大全

G1運作流程示意圖

G1垃圾收集器是JDK 9開始預設的垃圾收集器,它有如下特點:

  • 「并行與并發」:G1能充分利用多CPU、多核環境下的硬體優勢,使用多個CPU來縮短STW的時間。
  • 「不會産生記憶體碎片」:G1是基于“标記—整理”算法實作的收集器
  • 「可以指定停頓時間」:-XX:MaxGCPauseMillis參數指定目标的最大停頓時間,G1嘗試調整新生代和老年代的比例,堆大小,晉升政策來達到這個目标時間。
  • 「局部收集」:使用Region劃分記憶體空間,保證了G1收集器在有限的時間内擷取盡可能高的收集效率。

ZGC

ZGC(Z Garbage Collector)是一款在JDK 11中新加入的具有實驗性質的可擴充的低延遲垃圾收集器 ,在JDK 15中已經可以正式在生産中使用了。ZGC的目标是在任意堆記憶體大小下都可以把垃圾收集的停頓時間限制在10毫秒以内的低延遲。

ZGC也采用基于Region的堆記憶體布局,ZGC的Region具有動态性——動态建立和銷毀,以及動态的區域容量大小。在x64硬體平台下,ZGC的Region可以具有大、中、小三類容量:

  • 小型Region(Small Region):容量固定為2MB,用于放置小于256KB的小對象。
  • 中型Region(Medium Region):容量固定為32MB,用于放置大于等于256KB但小于4MB的對象。
  • 大型Region(Large Region):容量不固定,可以動态變化,但必須為2MB的整數倍,用于放置4MB或以上的大對象。每個大型Region中隻會存放一個大對象,這也預示着雖然名字叫作“大型Region”,但它的實際容量完全有可能小于中型Region,最小容量可低至4MB。
Hotspot垃圾收集器大全

ZGC堆記憶體布局

ZGC有如下特點:

  • 低延遲、停頓時間短(10ms以内)
  • 支援8M~16T記憶體的GC
  • 多線程并發GC
  • 采用染色指針技術實作對象引用
  • 不再使用分代收集
  • 支援不同容量的Region分區

垃圾收集器搭配及啟動

新生代 老年代 JVM參數 Serial SerialOld -XX:+UseSerialGC Parallel Scavenge SerialOld -XX:+UseParallelGC Parallel Scavenge ParallelOld -XX:+UseParallelOldGC ParNew SerialOld -XX:+UseParNewGC ParNew CMS + SerailOld -XX:+UseConcMarkSweepGC G1 G1 -XX:+UseG1GC ZGC ZGC -XX:+UseZGC

總結

本文主要簡單介紹了常見的垃圾收集器。其中ZGC了解學習一下就可以了,ZGC雖然在JDK 15中可以使用,但是具體表現如何不好說,也沒有被官方指定為預設垃圾收集器,對比之前的垃圾收集器可以發現,能留下來的都是曾經被官方指定為預設垃圾收集器,不被指定的在後續版本中就被移除了,比如CMS垃圾收集器。

繼續閱讀