這三大特性,讓 G1 取代了 CMS!

大家好,我是樹哥。

之前我們聊過 CMS 回收器,但那時候我們說 CMS 回收器已經落伍了,現在應該是用 G1 回收器的時候了。那麼 G1 回收器到底有什麼魔力,它比 CMS 回收器相比強在哪裏呢?今天,就讓樹哥帶大家盤一盤!

文章思維導圖

G1 回收器的歷史

G1(Garbage-First)回收器早在 JDK1.7 的時候就確定要做,但直到 JDK7u4 的時候才正式推出使用。等到了 JDK9 之後變成了默認的垃圾回收器,同時廢棄了 CMS 回收器。

G1 回收器特性

G1 回收器是一款面向服務端應用的垃圾回收器,它的長期實名是替換 CMS 回收器。

G1 回收器於 CMS 回收器相比,它們有相似的地方,例如:都是關注 GC 停頓時間的回收器,都採用了分代回收的思想。但從整體的實現上來看,G1 回收器做了非常多的改進,可以說是對 CMS 回收器的全面改進。相對於 CMS 回收器來說,G1 回收器有下面幾個不同的地方:

  1. 採用化整爲零的分區思想
  2. 採用標記-整理的垃圾回收算法
  3. 可預測的 GC 停頓時間

分區思想

對於 CMS 及之前的回收器來說,其 JVM 內存空間按照分代的思路劃分成物理連續的一大片區域,如下圖所示。

但在 G1 回收器中,雖然也採用了分代的思路,但其並沒有爲其分配一塊連續的內存,而是將整塊內存化整爲零拆分成一個個 Region,如下圖所示。

正如上圖所示,G1 回收器不再爲年輕代和老年代劃分大塊的內存,而是劃分成了一個個的 Region,每個 Region 被標記成年輕代或者老年代。在 G1 中,還多了一個 Humongous 區域,其是爲了優化大對象的分配而誕生的。

G1 回收器化整爲零的 Region 設計思想,是 G1 回收器比 CMS 回收器強大的核心。 通過將大塊的內存化整爲零,G1 回收器能夠更加靈活地控制 GC 停頓時間,並且也解決了 CMS 回收器存在的內存碎片問題以及大內存下的長 GC 停頓時間問題。

標記-整理算法

G1 回收器與 CMS 回收器的另一個很大的區別是:G1 回收器使用的是「標記-整理」算法,而 CMS 回收器使用的是「標記-清除」算法。 因此,CMS 回收器會產生非常多的內存碎片,而 G1 回收器則沒有這個困擾。

有些小夥伴會問:那爲什麼 CMS 回收器不用「標記-整理」算法呢?

很簡單,因爲 CMS 回收器的老年代很大,使用「標記-整理」算法需要耗費很長的 GC 停頓時間,這會導致接口響應時間變長。實際上 CMS 回收器後續提供了 -XX:+UseCMSCompactAtFullCollection 參數去實現內存壓縮,但在內存壓縮的時候 GC 停頓時間會很長,從而導致接口響應時間變長。

好奇寶寶又問了:G1 回收器也用的是「標記-整理」算法,爲啥就不會導致長 GC 停頓時間呢?

很簡單,因爲 G1 回收器使用了分 Region 的思想,其將大塊的內存化整爲零成爲 Region。此外,其還維護了一個待回收 Region 列表,可以選擇回收性價比最高的 Region 進行回收,從而實現對 GC 停頓時間的靈活控制。

看到了麼,G1 回收器化整爲零的 Region 設計思想,真的是 G1 回收器的大殺器!

可預測的停頓時間

G1 回收器對於 CMS 而言還有一個很大的優勢,即能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度爲 M 毫秒的時間片段內,消耗在垃圾收集上的時間不得超過 N 毫秒。對於該特性現在還用得比較少,大家瞭解一下就可以了。

垃圾回收過程

比起 CMS 回收器來說,G1 回收器的垃圾回收過程就比較特別了,其採用了「年輕代收集」和「混合收集」兩種垃圾回收方式。

年輕代收集

在應用剛剛啓動的時候,流量慢慢進來,JVM 開始生成對象。G1 會選擇一個分區並指定 eden 分區,當這塊分區用滿之後,G1 會選一個新的分區作爲 eden 分區。這個操作會一直進行下去,一直到達到 eden 分區的上限,接着觸發一次年輕代收集。

年代收集採用的是「複製算法」,其首先使用單 eden、雙 survivor 遷移存活對象。在遷移過程中,會根據對象年齡以及其他特性,將對象晉升到老年代分區中,原有的年輕代分區會被整個回收掉。這個過程涉及到的規則和 CMS 回收器類似,只是 G1 回收器將內存化整爲零了而已。

混合收集

隨着時間推移,越來越多的對象晉升到老年代中。當老年代佔比(佔 Java 堆內存的比例)達到 InitiatingHeapOccupancyPercent 參數之後,JVM 便會觸發「混合收集」進行垃圾收集。要注意的是:混合收集會收集年輕代和部分老年代的內存,其並不等同於 Full GC。Full GC 會回收整個老年代內存。

對於混合收集方式來說,其收集過程可以分爲 4 個階段:

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

初始標記。 該階段與 CMS 回收器一樣,都只是簡單標記一下 GC Roots 能直接關聯到的對象,讓後續 GC 回收線程能與用戶線程併發執行。初始標記階段是需要「Stop the World」的。

併發標記。 該階段與 CMS 回收器一樣,它從 GC Root 開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時很長,但可與用戶程序併發執行,不需要「Stop the World」。

最終標記。 該階段與 CMS 回收器一樣,它是爲了修正在併發標記期間因用戶程序繼續運作而導致引用發生變化的問題。只是 G1 回收器採用了不同的方式去實現,在這個階段是需要「Stop the World」的。

篩選回收。 該階段與 CMS 回收器的併發清除一樣,它是去將標記爲垃圾的對象清除掉。只是對於 G1 回收器來說,它會維護各個 Region 的回收價值和成本,隨後根據用戶期望的 GC 停頓時間來指定回收計劃。

來自《深入理解Java虛擬機》

整體看下來,我們會發現 G1 回收器的混合收集過程與 CMS 回收器非常類似,都經歷初始標記、併發標記、最終標記、篩選回收(併發清除)幾個階段。

總結

從 JDK7 正式推出到 JDK9 成爲默認的垃圾收集器,G1 回收器用了兩代人的時間打敗了 CMS 回收器。

從 G1 回收器的實現來看,其開創性的化整爲零的 Region 設計思想,無疑是其打敗 CMS 回收器的祕訣。通過該設計思想,G1 回收器得以更加靈活地控制 GC 停頓時間,同時也可以實現更加高效、複雜的功能,例如:根據回收空間和耗時選擇最佳的回收 Region、預測 GC 停頓時間等。

參考資料

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