【JVM】Java 中的經典垃圾回收器

從不同角度分析垃圾收集器,可以將其劃分爲不同的模型。

按線程數分,可以分爲串行垃圾回收器和並行垃圾回收器;按照工作模式分,可以分爲併發式垃圾回收器和獨佔式垃圾回收器;按碎片處理方式可分爲壓縮式垃圾回收器和非壓縮式垃圾回收器;而按工作的內存區間,又可分爲新生代垃圾回收器和老年代垃圾回收器。本文就基於工作的內存區間劃分,來介紹七種經典的垃圾回收器,下圖是它們的工作區間以及搭配方式。

Young generation

Serial 收集器

看名字就能猜到,這個收集器是一個單線程工作的收集器,但是它的“單線程”的意義並不僅僅是說明它只會使用一個處理器或一條收集線程去完成垃圾收集工作,更重要的是強調在它進行垃圾收集時,必須暫停其他所有工作線程,直到它工作結束。同時,其採用的是標記複製算法

Serial 是 JDK 1.3.1 之前新生代的唯一選擇,雖然看起來這個垃圾收集器好像老而無用了。但是事實上它依然是 HotSpot 虛擬機運行在客戶端模式下的默認新生代垃圾收集器,有着優於其他收集器的地方,那就是簡單而高效(與其他收集器的單線程相比),對於內存資源受限的環境,它是所有收集器裏額外內存消耗(Memory Footprint)最小的;

ParNew 收集器

ParNew 收集器實際上是 Serial 收集器的多線程並行版本,除了同時使用多條線程進行垃圾收集之外,其餘的行爲包括 Serial 收集器可用的所有控制參數、收集算法、Stop The World、對象分配規則、回收策略等都與 Serial 收集器完全一一致。

ParNew 收集器除了支持多線程並行收集外,其他與 Serial 收集器相比並沒有太多的創新之處,但它卻是不少運行在服務端模式下的 HotSpot 虛擬機,尤其是 JDK 7 之前的遺留系統首選的新生代收集器,其中有一個與功能、性能無關但其實很重要的原因:除了 Serial 收集器外,目前只有它能與 CMS 收集器配合工作

ParNew 收集器在單核心處理器的環境中絕對不會有比 Serial 收集器更好的效果。它默認開啓的收集線程與處理核心數量相同,在處理器核心非常多的環境下,可以使用 -XX:ParallelGCThreads 來限制垃圾回收器的線程數。

Parallel Scavenge 收集器

Parallel Scavenge 收集器也是一款新生代收集器,同樣是基於標記-複製算法實現的收集器,其從表面上看與 ParNew 十分相似,但它的關注點與其他收集器不同,CMS 等收集器的關注點是儘可能地縮短垃圾收集時用戶線程的停頓時間,而 Parallel Scavenge 收集器的目標則是達到一個可控制的吞吐量(Throughput)。

所謂吞吐量就是處理器用於運行用戶代碼的時間與處理器總消耗的時間的比值,即:

\[吞吐量 = \frac{運行用戶代碼時間}{運行用戶代碼時間 + 運行垃圾收集時間} \]

Tenured generation

Serial Old 收集器

Serial Old 是 Serial 收集器的老年代版本,同樣也是一個單線程工作的收集器,使用標記整理算法

這個收集器的主要意義也是提供客戶端模式下的 HotSpot 虛擬機使用。如果在服務端模式下,它也可能有兩種用途:一種是在 JDK 5 以前的版本中與 Parallel Scavenge 收集器搭配使用,另外一種就是做爲 CMS 收集器發生失敗時的後備預案,在併發收集發生 Concurrent Mode Failure 時使用。

Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,支持多線程併發收集,基於標記整理算法

其主要與 Parallel Scavenge 做搭配。

CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。從名字上就可以看出 CMS 收集器是基於標記-清除算法實現的,它的運作過程相對於前面幾種收集器來說要更復雜一些,整個過程分爲四個步驟,包括:

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

其中併發標記以及重新標記這兩個步驟仍然需要“Stop The World”。

初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快;

併發標記就是從GC Roots的直接關聯對象開始遍歷整個對象圖的過程,這個過程耗時很長但是不需要停頓用戶線程,可以與垃圾收集線程一起併發運行;

而重新標記階段則是爲了修正併發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍微長一些,但也遠比並發標記階段的時間短;

最後是併發清除階段,清除刪除掉標記階段判斷的已經死亡的對象,由於不需要移動存活對象,所以這個階段也是可以與用戶線程併發進行的。

CMS 的優點很明顯:併發收集、低停頓。但它也有很明顯的缺點:

  • CMS收集器對處理器資源非常敏感。事實上,面向併發設計的程序都對處理器資源比較敏感。在併發階段,它雖然不會導致用戶線程停頓,但卻會因爲佔用了一部分線程(或者說處理器的計算能力)而導致應用線程變慢,降低總吞吐量。

  • 由於CMS收集器無法處理“浮動垃圾”(Floating Garbage),有可能出現”Concurrent Mode Failure“失敗進而導致另一次完全”Stop The World“的 Full GC 的產生。

    浮動垃圾:併發清理階段用戶線程還在運行,這段時間內就可能產生新的垃圾,新的垃圾在此次 GC 無法清除,只能等到下次清理。這些垃圾有個專業的名詞:浮動垃圾;

  • CMS 是一款基於”標記-清除“的算法實現的垃圾收集器,這意味着收集結束時會有大量的空間碎片產生。空間碎片過多時,將會給大對象的分配帶來很大的麻煩,往往會出現老年代還有很多剩餘空間,但就是無法找到足夠大的連續空間來分配當前對象。

Garbage First

爲解決CMS算法產生空間碎片和其它一系列的問題缺陷,HotSpot提供了另外一種垃圾回收策略,G1(Garbage First)算法,通過參數-XX:+UseG1GC來啓用,該算法在JDK 7u4版本被正式推出,官網對此描述如下:

The Garbage-First (G1) collector is a server-style garbage collector, targeted for multi-processor machines with large memories. It meets garbage collection (GC) pause time goals with a high probability, while achieving high throughput. The G1 garbage collector is fully supported in Oracle JDK 7 update 4 and later releases. The G1 collector is designed for applications that:

  • Can operate concurrently with applications threads like the CMS collector.

  • Compact free space without lengthy GC induced pause times.

  • Need more predictable GC pause durations.

  • Do not want to sacrifice a lot of throughput performance.

  • Do not require a much larger Java heap.

在 G1 算法中,採用了另外一種完全不同以往的組織堆內存,堆內存被劃分爲多個大小相等的內存塊(Region),每個Region是邏輯連續的一段內存,結構如下:

每個Region被標記了E、S、O和H,說明每個Region在運行時都充當了一種角色,其中H是以往算法中沒有的,它代表Humongous,這表示這些Region存儲的是巨型對象(humongous object,H-obj),當新建對象大小超過Region大小一半時,直接在新的一個或多個連續Region中分配,並標記爲H。

G1 中提供了三種模式垃圾回收模式,young GC、mixed GC 和 full GC,在不同的條件下被觸發。

Young GC

發生在年輕代的GC算法,一般對象(除了巨型對象)都是在eden region中分配內存,當所有eden region被耗盡無法申請內存時,就會觸發一次 Young GC,這種觸發機制和之前的 young GC 差不多,執行完一次 Young GC,活躍對象會被拷貝到survivor region或者晉升到old region中,空閒的region會被放入空閒列表中,等待下次被使用。

Mixed GC

當越來越多的對象晉升到老年代old region時,爲了避免堆內存被耗盡,虛擬機會觸發一個混合的垃圾收集器,即 mixed gc,該算法並不是一個 Old GC,除了回收整個 Young region,還會回收一部分的 Old Region,這裏需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些 Old region 進行收集,從而可以對垃圾回收的耗時時間進行控制。

mixed GC 的執行過程有點類似 CMS,主要分爲以下幾個步驟:

  1. initial mark: 初始標記過程,整個過程 STW,標記了從GC Root可達的對象
  2. concurrent marking: 併發標記過程,整個過程 GC collector線程與應用線程可以並行執行,標記出GC Root可達對象衍生出去的存活對象,並收集各個Region的存活對象信息
  3. remark: 最終標記過程,整個過程 STW,標記出那些在併發標記過程中遺漏的,或者內部引用發生變化的對象
  4. clean up: 垃圾清除過程,如果發現一個Region中沒有存活對象,則把該 Region 加入到空閒列表中

Full GC

如果對象內存分配速度過快,Mixed GC 來不及回收,導致老年代被填滿,就會觸發一次 Full GC,G1 的 Full GC 算法就是單線程執行的 serial old gc,會導致異常長時間的暫停時間,需要進行不斷的調優,儘可能的避免 Full GC.

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