上一課時我們講了垃圾回收的理論知識,而本課時將介紹這些理論知識的具體實踐。垃圾回收器也叫垃圾收集器,不同的廠商對垃圾收集器的實現也是不同的,這里主要介紹目前使用最廣泛的 OracleJDK 中自帶的 HotSpot 虛擬機中的幾個垃圾收集器。
我們本課時的面試題是,你用過哪些垃圾回收器?它們有什么區別?
#### 典型回答
《Java 虛擬機規范》并沒有對垃圾收集器的具體實現做任何的規定,因此每家垃圾收集器的實現方式都不同,但比較常用的垃圾回收器是 OracleJDK 中自帶的 HotSpot 虛擬機。HotSpot 中使用的垃圾收集器主要包括 7 個:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS 和 G1(Garbage First)收集器。
其中 Serial 收集器屬于最早期的垃圾收集器,也是 JDK 1.3 版本之前唯一的垃圾收集器。它是單線程運行的垃圾收集器,其單線程是指在進行垃圾回收時所有的工作線程必須暫停,直到垃圾回收結束為止,如下圖所示:

Serial 收集器的特點是簡單和高效,并且本身的運行對內存要求不高,因此它在客戶端模式下使用的比較多。
ParNew 收集器實際上是 Serial 收集器的多線程并行版本,運行示意圖如下圖所示:

Parallel Scavenge 收集器和 ParNew 收集器類似,它也是一個并行運行的垃圾回收器;不同的是,該收集器關注的側重點是實現一個可以控制的吞吐量。而這個吞吐量計算的也很奇怪,它的計算公式是:用戶運行代碼的時間 / (用戶運行代碼的時間 + 垃圾回收執行的時間)。比如用戶運行的時間是 8 分鐘,垃圾回收運行的時間是 2 分鐘,那么吞吐量就是 80%。Parallel Scavenge 收集器追求的目標就是將這個吞吐量的值,控制在一定的范圍內。
Parallel Scavenge 收集器有兩個重要的參數:
* -XX:MaxGCPauseMillis 參數:它是用來控制垃圾回收的最大停頓時間;
* -XX:GCTimeRatio 參數:它是用來直接設置吞吐量的值的。
Serial Old 收集器為 Serial 收集器的老年代版本,而 Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本。
CMS(Concurrent Mark Sweep)收集器與以吞吐量為目標的 Parallel Scavenge 收集器不同,它強調的是提供最短的停頓時間,因此可能會犧牲一定的吞吐量。它主要應用在 Java Web 項目中,它滿足了系統需要短時間停頓的要求,以此來提高用戶的交互體驗。
Garbage First(簡稱 G1)收集器是歷史發展的產物,也是一款更先進的垃圾收集器,主要面向服務端應用的垃圾收集器。它將內存劃分為多個 Region 分區,回收時則以分區為單位進行回收,這樣它就可以用相對較少的時間優先回收包含垃圾最多區塊。從 JDK 9 之后也成了官方默認的垃圾收集器,官方也推薦使用 G1 來代替選擇 CMS 收集器。
#### 考點分析
JVM 內存布局和垃圾回收算法是面試中常考的題目,也是我們理解并優化 Java 程序的理論基礎,而對于垃圾收集器來說除了目前主流版本(JDK 8)常用的 CMS 之外,其他的垃圾收集器都屬于面試中的加分項。對于 G1 和 JDK 11 中的 ZGC 的理解代表了你對技術的熱愛和新技術的敏感程度,也屬于面試中的重要加分項。
和此知識點相關的面試題還有以下這些:
* 講一下分代收集理論?
* CMS 收集器的具體執行流程是什么?
* 講一下 JDK 11 中的 ZGC 收集器?
#### 知識擴展
* [ ] 1. 分代收集
說到垃圾收集器不得不提的一個理論就是“分代收集”,因為目前商用虛擬機的垃圾收集器都是基于分代收集的理論進行設計的,它是指將不同“年齡”的數據分配到不同的內存區域中進行存儲,所謂的“年齡”指的是經歷過垃圾收集的次數。這樣我們就可以把那些朝生暮死的對象集中分配到一起,把不容易消亡的對象分配到一起,對于不容易死亡的對象我們就可以設置較短的垃圾收集頻率,這樣就能消耗更少的資源來實現更理想的功能了。
通常情況下分代收集算法會分為兩個區域:新生代(Young Generation)和老年代(OldGeneration),其中新生代用于存儲剛剛創建的對象,這個區域內的對象存活率不高,而對于經過了一定次數的 GC 之后還存活下來的對象,就可以成功晉級到老生代了。
對于上面介紹的 7 個垃圾收集器來說,新生代垃圾收集器有:Serial、ParNew、Parallel Scavenge,老生代的垃圾收集器有:Serial Old、Parallel Old、CMS,而 G1 屬于混合型的垃圾收集器,如下圖所示:

* [ ] 2. CMS 收集器的具體執行流程
CMS 收集器是基于標記-清除算法實現的,我們之前有講過關于標記-清除的算法,這里簡單地回顧一下。標記-清除的算法是由標記階段和清除階段構成的,標記階段會給所有的存活對象做上標記;而清除階段會把被標記為死亡的對象進行回收,而死亡對象的判斷是通過引用計數法或者是目前主流的可達性分析算法實現的。但是 CMS 的實現稍微復雜一些,它的整個過程可以分為四個階段:
* 初始標記(CMS initial mark)
* 并發標記(CMS concurrent mark)
* 重新標記(CMS remark)
* 并發清除(CMS concurrent sweep)
首先,初始標記階段的執行時間很短,它只是標記一下 GC Roots 的關聯對象;并發階段是從 GC Roots 關聯的對象進行遍歷判斷并標識死亡對象,這個過程比較慢,但不需要停止用戶線程,用戶的線程可以和垃圾收集線程并發執行;而重新標記階段則是為了判斷并標記,剛剛并發階段用戶繼續運行的那一部分對象,所以此階段的執行時間也比較短;最后是并發清除階段,也就是清除上面標記的死亡對象,由于 CMS 使用的是標記-清除算法,而非標記-整理算法,因此無須移動存活的對象,這個階段垃圾收集線程也可以和用戶線程并發執行。
CMS 的整個執行過程中只有執行時間很短的初始標記和重新標記需要 Stop The World(全局停頓)的,執行過程如下圖所示:

因為 CMS 是一款基于標記清除算法實現的垃圾收集器,因此會在收集時產生大量的空間碎片,為了解決這個問題,CMS 收集器提供了一個 -XX:+UseCMS-CompactAtFullCollection 的參數(默認是開啟的,此參數從 JDK9 開始廢棄),用于在 CMS 收集器進行 Full GC 時開啟內存碎片的合并和整理。
但又因為碎片整理的過程必須移動存活的對象,所以它和用戶線程是無法并發執行的,為了解決這個問題 CMS 收集器又提供了另外一個參數 -XX:CMSFullGCsBefore-Compaction,用于規定多少次(根據此參數的值決定)之后再進行一次碎片整理。
* [ ] 3. ZGC
ZGC 收集器是 JDK 11 中新增的垃圾收集器,它是由 Oracle 官方開發的,并且支持 TB 級別的堆內存管理,而且 ZGC 收集器也非常高效,可以做到 10ms 以內完成垃圾收集。
在 ZGC 收集器中沒有新生代和老生代的概念,它只有一代。ZGC 收集器采用的著色指針技術,利用指針中多余的信息位來實現著色標記,并且 ZGC 使用了讀屏障來解決 GC 線程和應用線程可能存在的并發(修改對象狀態的)問題,從而避免了Stop The World(全局停頓),因此使得 GC 的性能大幅提升。
ZGC 的執行流程和 CMS 比較相似,首先是進行 GC Roots 標記,然后再通過指針進行并發著色標記,之后便是對標記為死亡的對象進行回收(被標記為橘色的對象),最后是重定位,將 GC 之后存活的對象進行移動,以解決內存碎片的問題。
#### 小結
本課時我們介紹了 JDK 11 之前的 7 種垃圾收集器:Serial、Serial Old、ParNew、Parallel Scavenge、Parallel Old、CMS、G1,其中 CMS 收集器是 JDK 8 之前的主流收集器,而 JDK 9 之后的默認收集器為 G1,并且在文章的最后,介紹了性能更加強悍、綜合表現更好的 ZGC 收集器,希望本課時的內容可以切實的幫助到你。
####
課后問答
- 前言
- 開篇詞
- 開篇詞:大廠技術面試“潛規則”
- 模塊一:Java 基礎
- 第01講:String 的特點是什么?它有哪些重要的方法?
- 第02講:HashMap 底層實現原理是什么?JDK8 做了哪些優化?
- 第03講:線程的狀態有哪些?它是如何工作的?
- 第04講:詳解 ThreadPoolExecutor 的參數含義及源碼執行流程?
- 第05講:synchronized 和 ReentrantLock 的實現原理是什么?它們有什么區別?
- 第06講:談談你對鎖的理解?如何手動模擬一個死鎖?
- 第07講:深克隆和淺克隆有什么區別?它的實現方式有哪些?
- 第08講:動態代理是如何實現的?JDK Proxy 和 CGLib 有什么區別?
- 第09講:如何實現本地緩存和分布式緩存?
- 第10講:如何手寫一個消息隊列和延遲消息隊列?
- 模塊二:熱門框架
- 第11講:底層源碼分析 Spring 的核心功能和執行流程?(上)
- 第12講:底層源碼分析 Spring 的核心功能和執行流程?(下)
- 第13講:MyBatis 使用了哪些設計模式?在源碼中是如何體現的?
- 第14講:SpringBoot 有哪些優點?它和 Spring 有什么區別?
- 第15講:MQ 有什么作用?你都用過哪些 MQ 中間件?
- 模塊三:數據庫相關
- 第16講:MySQL 的運行機制是什么?它有哪些引擎?
- 第17講:MySQL 的優化方案有哪些?
- 第18講:關系型數據和文檔型數據庫有什么區別?
- 第19講:Redis 的過期策略和內存淘汰機制有什么區別?
- 第20講:Redis 怎樣實現的分布式鎖?
- 第21講:Redis 中如何實現的消息隊列?實現的方式有幾種?
- 第22講:Redis 是如何實現高可用的?
- 模塊四:Java 進階
- 第23講:說一下 JVM 的內存布局和運行原理?
- 第24講:垃圾回收算法有哪些?
- 第25講:你用過哪些垃圾回收器?它們有什么區別?
- 第26講:生產環境如何排除和優化 JVM?
- 第27講:單例的實現方式有幾種?它們有什么優缺點?
- 第28講:你知道哪些設計模式?分別對應的應用場景有哪些?
- 第29講:紅黑樹和平衡二叉樹有什么區別?
- 第30講:你知道哪些算法?講一下它的內部實現過程?
- 模塊五:加分項
- 第31講:如何保證接口的冪等性?常見的實現方案有哪些?
- 第32講:TCP 為什么需要三次握手?
- 第33講:Nginx 的負載均衡模式有哪些?它的實現原理是什么?
- 第34講:Docker 有什么優點?使用時需要注意什么問題?
- 彩蛋
- 彩蛋:如何提高面試成功率?