在上篇文章中介紹了Java GC:基礎原理,這篇文章來看下在 JVM 中是如何實現具體的收集器的。git
JVM 提供了多種垃圾收集器用來分別收集新生代和老年代,新老收集器之間能夠組合使用,可是在實際使用中(基於Java 8),通常常見的有4種收集器組合github
PS: 在 Java 8 中 -XX:+UseParallelOldGC 和 -XX:+UseParNewGC 已經沒法單獨使用算法
下面咱們從原理上去理解每一個收集器是如何工做的,但並不會去細究具體的實現。數據結構
Serial GC 使用 mark-copy 算法處理新生代,使用 mark-sweep-compact 算法處理老年代。正如名字所說的那樣,這是一個單線程的收集器。整個收集過程會觸發 stop-the-world 暫停應用,直到回收結束。該收集器適用於只有幾百M大小的堆和單核 CPU的狀況。對於服務端,不多會使用這個組合,由於沒法合理的使用計算機資源,不過反過來講的話,這也能夠知足對系統資源使用有限制的狀況。多線程
Parallel GC 和 Serial GC 使用的算法同樣。只不過是多線程的,是 JVM 默認使用的 GC 。和 Serial GC 同樣,在整個收集過程會觸發 stop-the-world 暫停應用,直到回收結束。在多核環境下,處理速度要比 Serial GC 快,能夠提升吞吐量。併發
Serial GC 和 Parallel GC 在回收垃圾的過程當中須要暫停應用線程,所以若是對應用延遲有要求的話,CMS 收集器是一個更好的選擇。其使用 stop-the-world 的 mark-copy 算法收集新生代(ParNew,和 Parallel GC 類似但不兼容,只和 CMS 一塊兒使用),使用幾乎和應用線程併發的 mark-sweep 算法收集老年代。post
PS :至於 ParNew 和 Parallel 的關係是有歷史緣由的,有興趣的能夠看這個爲何有Parallel GC 了,還要有個 ParNew,還不兼容?線程
CMS 主要用於避免老年代回收時長時間的暫停。首先,其使用了 mark-sweep 算法而不進行壓縮(注意因爲會產生內存碎片,所以在內存分配上使用了 free-lists 而不是指針碰撞,因此分配速度會相對慢一些) 。其次,在 mark-sweep 算法收集過程當中,與應用程序幾乎是併發的,幾乎不影響應用程序的執行(暫停的時間很短)。設計
PS:CMS爲何沒有采用標記-整理算法來實現?3d
不過凡事有利即有弊
CMS 的併發收集過程以下:
此外,除了被動的由 Mionr GC觸發 Old GC 外,在實際應用中,還存在着主動的,週期性的 Old GC,只不過存在着觸發條件,有興趣的能夠自行額外查閱。
雖然 JDK 9 已經廢除 CMS 垃圾收集器,但 CMS 仍然是 JDK 8 及之前的追求低延遲的不錯的可選項 。
爲解決 CMS 算法產生的一系列問題缺陷,HotSpot提供了另一種垃圾回收策略,從 JDK 9 開始,G1 取代 Parallel GC 成爲 JVM 中的默認 GC。G1 的其中一個核心目標是使 stop-the-world 的時間可預測而且可配置。好比說,你能夠要求每分鐘 stop-the-world 的暫停時間不能超過 5 毫秒,G1 會盡最大可能去完成需求,但並非必定能達到的。
不像以前的堆內存是按分代連續分佈的,G1 將堆劃分紅了必定數量(典型的如2048個)的小區域(Region),每一個小區域多是 Eden, Survior 或者 Old 區域。在邏輯上,Eden 和 Survivor 區域組成了新生代,Old 區域組成了老年代。這個設計容許 GC 每一個只收集其中的一些區域而不是整個堆。不過新生代區域每次都會參與。另外一個比較新奇的特色是 G1 能夠估計每一個區域存活對象的多少,存活數量多的區域會先被收集,這也是這個收集器名字的由來,garbage first 收集器。
G1 大體可分爲 Young GC 和 Mixed GC,Young GC 處理全部的 Young Region, Mixed GC 處理全部的 Young Region 和 部分的 Old Region,至於選擇哪些 Old Region,須要併發標記來蒐集必要的信息。分代式G1的正常工做流程就是在 young GC 與 mixed GC 之間視狀況切換,背後按期作併發標記蒐集資料。
在應用程序剛開始的時候,G1 沒有額外的信息去運行併發階段。所以在這個階段,G1 的運做模式和其餘的新生代收集器很像,須要 stop-the-world,使用複製算法,將存活對象的複製到 Survivor 區域中,或者空閒區域中(以後這個區域也是 Survivor 區)。該階段能夠理解爲 Young GC (Minor GC),所謂疏散,其實就是 copy 到其餘區域中。
G1 收集器的不少概念是創建在 CMS 上的,所以在流程上會有一些類似。儘管如此,它們之間也有不少不同的地方,好比 CMS 則是採用增量更新的方式,即額外 mark 修改的引用再作處理,而 G1 使用了 SATB (Snapshot-At-The-Beginning) 來維持併發 GC 的正確性 。Snapshot-At-The-Beginning,從字面上理解,就是在 GC 開始時產生一個全部存活對象的邏輯快照,在這個快照中存活的對象,加上以後在 GC 過程當中新分配的對象認爲是最終的存活對象,其它不可到達的對象就是死的了。關於 SATB 具體是如何工做的,能夠看R大寫過的 G1 講解
和 CMS 同樣,該階段具體的能夠分爲如下幾個階段,每一個階段的目的和 CMS 差很少,只是具體實現上會有區別:
其中 Initial Mark 在 Evacuation Pause 中捎帶完成了。注意這裏的 cleanup,並非真的在堆上清理了實際對象,而是統計每一個 region 存活數量的多少,並按預期的 GC 效率對它們進行排序,爲 mixed GC 作準備。但有例外,若是發現這個 region 都沒有存活對象,整個區域會被回收到可分配的 region 列表中。
這個階段並不必定緊跟在併發標記以後,須要知足必定的條件,好比,若是能夠同時釋放一大部分的老年代,不然該階段沒有必要。所以,在併發標記結束和混合疏散暫停之間可能很容易出現一些只回收年輕的疏散階段。
該階段選定全部新生代裏的 region,外加根據併發標記統計得出收集收益高的若干個老年代 region(在用戶指定的開銷目標範圍內儘量選擇收益高的old gen region)進行回收。