1. 程式人生 > >Java常用的垃圾收集器

Java常用的垃圾收集器

在上一篇文章中,我們介紹了Java的垃圾回收機制,包括什麼時候回收垃圾,標記垃圾的演算法以及回收垃圾的演算法。這篇文章我們主要來介紹Java的垃圾收集器。

在介紹垃圾收集器之前,我們首先需要知道一些必要的概念。

Stop the world

顧名思義,“Stop the world”就是  JVM 由於要執行 GC 而停止了其他應用程式的執行,在任何 GC 演算法中都可能會發生。假設有這麼一個場景,你的程式正在愉快的執行,突然之間 JVM 要清理垃圾了。然後程式就陷入了10分鐘的等待,是不是很抓狂?當然一般情況下會讓你等待這麼久,但是“Stop the world”會在一定程度上影響使用者體驗這是毋庸置疑的。所以,多數GC優化通過減少 Stop-the-world 時間來提升系統性能。

垃圾收集器和垃圾回收演算法的關係

說完“Stop the world”我們回到正題,回到我們垃圾收集器的模組,如果說垃圾收集演算法是記憶體回收的方法論,那麼垃圾收集器那麼垃圾收集器就是記憶體回收的具體實現。

我們費盡心機對垃圾收集器進行比較,目的不是挑出一個最好的垃圾收集器,而是找到最合適的。 現在為止還沒有最好的垃圾收集器出現,更沒有說出現萬能的垃圾收集器,我們能做的就是根據具體應用場景選擇適合自己的垃圾收集器。試想一下:如果 “完美垃圾收集器” 真的面世了,我們還需要做這些工作嗎?

常用的垃圾收集器

Serial 收集器

Serial收集器是最基本、歷史最悠久的垃圾收集器。它是一個單執行緒收集器,“單執行緒” 的意義不僅僅意味著它只會使用一條垃圾收集執行緒去完成垃圾收集工作,更重要的是 它在進行垃圾收集工作的時候必須暫停其他所有的工作執行緒( "Stop The World" ),直到它收集結束。 它會在使用者不可見的情況下把使用者正常工作的執行緒全部停掉。想象一下,當你結束一天的工作回到家中,喝著冰闊樂刷著副本正要打Boss,突然你的電腦說他要休息5分鐘,你會是什麼感覺?

存在即合理,當然Serial 收集器也有優於其他垃圾收集器的地方,它簡單而高效(與其他收集器的單執行緒相比)。Serial 收集器由於沒有執行緒互動的開銷,自然可以獲得很高的單執行緒收集效率。Serial 收集器對於執行在 Client 模式下的虛擬機器來說是個不錯的選擇。
它的 新生代採用複製演算法,老年代採用標記整理演算法。

ParNew 收集器

ParNew 收集器是 Serial 收集器的多執行緒版本,除了使用多執行緒進行垃圾收集之外,其餘行為(控制引數、收集演算法、分配規則、回收策略等等)和 Serial 收集器完全一樣。

除了支援多執行緒收集,ParNew 相對 Serial 似乎並沒有太多改進的地方。但是它卻是許多執行在 Server 模式下的虛擬機器的首要選擇,除了 Serial 收集器外,只有它能與 CMS 收集器(真正意義上的併發收集器,後面會介紹到)配合工作。ParNew單核狀態下不如Serial,多核執行緒下才有優勢。

新生代採用複製演算法,老年代採用標記整理演算法。

Parallel Scavenge 收集器

Parallel Scavenge 收集器是一個新生代收集器,也是採用複製演算法+並行。聽起來和ParNew差不多對不對,那麼它有什麼特別之處呢?

Parallel Scavenge 收集器關注點是吞吐量(CPU執行程式碼的時間與CPU總消耗時間的比值)。 而CMS 等垃圾收集器的關注點更多的是縮短使用者執行緒的停頓時間(提高使用者體驗)。停頓時間越短就越適合和使用者進行互動(響應速度快,可以優化使用者體驗),而高吞吐量則可以高效的利用CPU時間,儘快完成使用者的計算任務。

Parallel Scavenge 收集器提供了很多引數供使用者找到最合適的停頓時間或最大吞吐量,如果對於收集器運作不太瞭解的話,手工優化存在的話可以選擇把記憶體管理優化交給虛擬機器去完成也是一個不錯的選擇。

Serial Old 收集器

Serial 收集器的老年代版本,它同樣是一個單執行緒收集器。它主要有兩大用途:一種用途是在 JDK1.5 以及以前的版本中與 Parallel Scavenge 收集器搭配使用,另一種用途是作為 CMS 收集器的後備方案。

新生代採用複製演算法,老年代採用標記整理演算法。

Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本。使用多執行緒和“標記-整理”演算法。在注重吞吐量以及 CPU 資源的場合,都可以優先考慮 Parallel Scavenge 收集器和 Parallel Old 收集器。

CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。它非常重視服務的響應速度,以期給使用者最好的體驗。。

從名字中的Mark Sweep這兩個詞可以看出,CMS 收集器是一種 “標記-清除”演算法實現的,它的運作過程相比於前面幾種垃圾收集器來說更加複雜一些。整個過程分為四個步驟:

  • 初始標記 需要“Stop the world”,僅僅只是標記一下GC Roots 能直接關聯到的物件,速度很快。

  • 併發標記 併發追溯標記,程式不會停頓。

  • 重新標記 需要“Stop the world”修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄

  • 併發清除 清理垃圾物件,程式不會停頓

CMS一款優秀的垃圾收集器,主要優點:併發收集、低停頓。但是它有下面三個明顯的缺點:

  • 對 CPU 資源敏感;
  • 無法處理浮動垃圾;
  • 它使用的回收演算法-“標記-清除”演算法會導致收集結束時會有大量空間 碎片產生。

G1 收集器

G1 (Garbage-First) 是一款面向伺服器的垃圾收集器,開發人員希望在未來可以換掉CMS收集器,它有如下特點

  • 並行與併發:G1 能充分利用 CPU、多核環境下的硬體優勢,使用多個 CPU(CPU 或者 CPU 核心)來縮短 Stop-The-World 停頓時間。部分其他收集器原本需要停頓 Java 執行緒執行的 GC 動作,G1 收集器仍然可以通過併發的方式讓 java 程式繼續執行。
  • 分代收集:雖然 G1 可以不需要其他收集器配合就能獨立管理整個 GC 堆,但是還是保留了分代的概念。
  • 空間整合:與 CMS 的“標記--清理”演算法不同,G1 從整體來看是基於“標記整理”演算法實現的收集器;從區域性上來看是基於“複製”演算法實現的。這就意味著不會產生大量的記憶體碎片
  • 可預測的停頓:這是 G1 相對於 CMS 的另一個大優勢,降低停頓時間是 G1 和 CMS 共同的關注點,但 G1 除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為 M 毫秒的時間片段內。

G1 收集器的運作大致分為以下幾個步驟:

  • 初始標記
  • 併發標記
  • 最終標記
  • 篩選回收

G1收集器將整個Java堆記憶體劃分為若干個記憶體大小相等的Region,年輕代和老年代不再物理隔離,他們都是一部分Region的集