JVM系列(六) - JVM垃圾回收器

前言

在以前的幾篇博客中,咱們大體介紹了,常見的 垃圾回收算法JVM 中常見的分類回收算法。這些都是從算法和規範上分析 Java 中的垃圾回收,屬於方法論。在 JVM 中,垃圾回收的具體實現是由 垃圾回收器Garbage Collector)負責的。算法

正文

概述

在瞭解 垃圾回收器 以前,首先得了解一下垃圾回收器的幾個名詞。編程

1. 吞吐量

CPU 用於運行用戶代碼的時間與 CPU 總消耗時間的比值。好比說虛擬機總運行了 100 分鐘,用戶代碼 時間 99 分鐘,垃圾回收 時間 1 分鐘,那麼吞吐量就是 99%後端

吞吐量 = 運行用戶代碼時間/(運行用戶代碼時間 + 垃圾回收時間)緩存

2. 停頓時間

停頓時間 指垃圾回收器正在運行時,應用程序暫停時間。對於 獨佔回收器 而言,停頓時間可能會比較長。使用 併發回收器 時,因爲垃圾回收器和應用程序 交替運行,程序的 停頓時間 會變短,可是,因爲其 效率 極可能不如獨佔垃圾回收器,故系統的 吞吐量 可能會較低。多線程

3. GC的名詞

3.1. 新生代GC(Minor GC)

指發生在 新生代 的垃圾回收動做,由於 Java 對象大多都具有 朝生夕死 的特性,因此 Minor GC 一般 很是頻繁,通常回收速度也比較快。架構

3.2. 老年代GC(Major GC)

指發生在 老年代 的垃圾回收動做,出現了 Major GC,常常會伴隨至少一次的 Minor GC(發生這種狀況,那麼 整個堆GC 一遍,一般稱爲 Full GC)。Major GC 的速度通常會比 Minor GC10 倍以上。併發

4. 併發與並行

4.1. 串行(Parallel)

單線程 進行垃圾回收工做,但此時 用戶線程 仍然處於 等待狀態框架

4.2. 併發(Concurrent)

這裏的併發指 用戶線程垃圾回收線程 交替執行。異步

4.3. 並行(Parallel)

這裏的並行指 用戶線程 和多條 垃圾回收線程 分別在不一樣 CPU 上同時工做。分佈式

垃圾回收算法

1. 根搜索算法

根搜索算法 是從 離散數學 中的圖論引入的,程序把全部引用關係看做一張圖,從一個節點 GC ROOT 開始,尋找對應的 引用節點,找到這個節點後,繼續尋找 這個節點引用節點。當全部的引用節點尋找完畢後,剩餘的節點 則被認爲是 沒有被引用到 的節點,即 無用 的節點。

上圖 紅色 爲無用的節點,能夠被 回收。目前 Java 中能夠做爲 GC ROOT 的對象有:

  1. 虛擬機棧 中引用的對象(本地變量表);

  2. 方法區靜態變量 引用的對象;

  3. 方法區常量 引用的對象;

  4. 本地方法棧 中引用的對象(Native 對象)。

基本全部 GC 算法都引用 根搜索算法 這種概念。

2. 標記 - 清除算法

標記-清除算法根集合 進行掃描,對 存活的對象 進行 標記。標記完畢後,再掃描整個空間中 未被標記 的對象進行 直接回收,以下圖所示:

標記-清除算法 不須要進行 對象的移動,而且僅對 不存活 的對象進行處理,在 存活 的對象 比較多 的狀況下 極爲高效。但因爲 標記-清除算法 直接回收不存活的對象,並無對還存活的對象進行 整理,所以會致使 內存碎片

3. 複製算法

複製算法 將內存劃分爲 兩個區間,使用此算法時,全部 動態分配 的對象都只能分配在 其中一個 區間(活動區間),而 另一個 區間(空間區間)則是 空閒 的。

複製算法 一樣從 根集合 掃描,將 存活 的對象 複製空閒區間。當掃描完畢活動區間後,會的將 活動區間 一次性所有 回收。此時本來的 空閒區間 變成了 活動區間。下次 GC 時候又會重複剛纔的操做,以此循環。

複製算法 在存活對象 比較少 的時候,極爲高效,可是帶來的成本是 犧牲一半的內存空間 用於進行 對象的移動。因此 複製算法 的使用場景,必須是對象的 存活率很是低 才行。最重要的是,咱們須要克服 50%內存浪費

4. 標記 - 整理算法

標記-整理算法 採用 標記-清除算法 同樣的方式進行對象的 標記,但在回收 不存活的對象 佔用的空間後,會將全部 存活的對象 往 左端空閒空間 移動,並更新對應的指針。

標記-整理 是在 標記-清除 之上,又進行了 對象的移動排序整理,所以 成本更高,但卻解決了 內存碎片 的問題。

JVM 爲了 優化內存 的回收,使用了 分代回收 的方式。對於 新生代內存 的回收(Minor GC)主要採用 複製算法。而對於 老年代內存 的回收(Major GC),大多采用 標記-整理算法

垃圾回收器

1. 垃圾回收器分類標準

2. 七種垃圾回收器概述

JVM 中,具體實現有 SerialParNewParallel ScavengeCMSSerial Old(MSC)Parallel OldG1 等。在下圖中,你能夠看到 不一樣垃圾回收器 適合於 不一樣的內存區域,若是兩個垃圾回收器之間 存在連線,那麼表示二者能夠 配合使用

若是當 垃圾回收器 進行垃圾清理時,必須 暫停 其餘全部的 工做線程,直到它徹底收集結束。咱們稱這種須要暫停工做線程才能進行清理的策略爲 Stop-the-World。以上回收器中, SerialParNewParallel ScavengeSerial OldParallel Old 均採用的是 Stop-the-World 的策略。

圖中有 7 種不一樣的 垃圾回收器,它們分別用於不一樣分代的垃圾回收。

  • 新生代回收器:Serial、ParNew、Parallel Scavenge

  • 老年代回收器:Serial Old、Parallel Old、CMS

  • 整堆回收器:G1

兩個 垃圾回收器 之間有連線表示它們能夠 搭配使用,可選的搭配方案以下:

新生代 老年代
Serial Serial Old
Serial CMS
ParNew Serial Old
ParNew CMS
Parallel Scavenge Serial Old
Parallel Scavenge Parallel Old
G1 G1

3. 單線程垃圾回收器

3.1. Serial(-XX:+UseSerialGC)

Serial 回收器是最基本的 新生代 垃圾回收器,是 單線程 的垃圾回收器。因爲垃圾清理時,Serial 回收器 不存在 線程間的切換,所以,特別是在單 CPU 的環境下,它的 垃圾清除效率 比較高。對於 Client 運行模式的程序,選擇 Serial 回收器是一個不錯的選擇。

Serial 新生代回收器 採用的是 複製算法

3.2. Serial Old(-XX:+UseSerialGC)

Serial Old 回收器是 Serial 回收器的 老生代版本,屬於 單線程回收器,它使用 標記-整理 算法。對於 Server 模式下的虛擬機,在 JDK1.5 及其之前,它常與 Parallel Scavenge 回收器配合使用,達到較好的 吞吐量,另外它也是 CMS 回收器在 Concurrent Mode Failure 時的 後備方案

Serial 回收器和 Serial Old 回收器的執行效果以下:

Serial Old 老年代回收器 採用的是 標記 - 整理算法

4. 多線程垃圾回收器(吞吐量優先)

4.1. ParNew(-XX:+UseParNewGC)

ParNew 回收器是在 Serial 回收器的基礎上演化而來的,屬於 Serial 回收器的 多線程版本,一樣運行在 新生代區域。在實現上,二者共用不少代碼。在不一樣運行環境下,根據 CPU 核數,開啓 不一樣的線程數,從而達到 最優 的垃圾回收效果。對於那些 Server 模式的應用程序,若是考慮採用 CMS 做爲 老生代回收器 時,ParNew 回收器是一個不錯的選擇。

ParNew 新生代回收器 採用的是 複製算法

4.2. Parallel Scavenge(-XX:+UseParallelGC)

ParNew 回收同樣,Parallel Scavenge 回收器也是運行在 新生代區域,屬於 多線程 的回收器。但不一樣的是,ParNew 回收器是經過控制 垃圾回收線程數 來進行參數調整,而 Parallel Scavenge 回收器更關心的是 程序運行的吞吐量。即一段時間內,用戶代碼 運行時間佔 總運行時間 的百分比。

Parallel Scavenge 新生代回收器 採用的是 複製算法

4.3. Parallel Old(-XX:+UseParallelOldGC)

Parallel Old 回收器是 Parallel Scavenge 回收器的 老生代版本,屬於 多線程回收器,採用 標記-整理算法Parallel Old 回收器和 Parallel Scavenge 回收器一樣考慮了 吞吐量優先 這一指標,很是適合那些 注重吞吐量CPU 資源敏感 的場合。

Parallel Old 老年代回收器 採用的是 標記 - 整理算法

5. 其餘的回收器(停頓時間優先)

5.1. CMS(-XX:+UseConcMarkSweepGC)

CMS(Concurrent Mark Sweep) 回收器是在 最短回收停頓時間 爲前提的回收器,屬於 多線程回收器,採用 標記-清除算法

相比以前的回收器,CMS 回收器的運做過程比較複雜,分爲四步:

  1. 初始標記(CMS initial mark)

初始標記 僅僅是標記 GC Roots直接關聯 的對象。這個階段 速度很快,須要 Stop the World

  1. 併發標記(CMS concurrent mark)

併發標記 進行的是 GC Tracing,從 GC Roots 開始對堆進行 可達性分析,找出 存活對象

  1. 從新標記(CMS remark)

從新標記 階段爲了 修正 併發期間因爲 用戶進行運做 致使的 標記變更 的那一部分對象的 標記記錄。這個階段的 停頓時間 通常會比 初始標記階段 稍長一些,但遠比 併發標記 的時間短,也須要 Stop The World

  1. 併發清除(CMS concurrent sweep)

併發清除 階段會清除垃圾對象。

初始標記CMS initial mark)和 從新標記CMS remark)會致使 用戶線程 卡頓,Stop the World 現象發生。

在整個過程當中,CMS 回收器的 內存回收 基本上和 用戶線程 併發執行,以下所示:

因爲 CMS 回收器 併發收集停頓低,所以有些地方成爲 併發低停頓回收器Concurrent Low Pause Sweep Collector)。

CMS 回收器的缺點:

  1. CMS回收器對CPU資源很是依賴

CMS 回收器過度依賴於 多線程環境,默認狀況下,開啓的 線程數(CPU 的數量 + 3)/ 4,當 CPU 數量少於 4 個時,CMS用戶查詢 的影響將會很大,由於他們要分出一半的運算能力去 執行回收器線程

  1. CMS回收器沒法清除浮動垃圾

因爲 CMS 回收器 清除已標記的垃圾 (處於最後一個階段)時,用戶線程 還在運行,所以會有新的垃圾產生。可是這部分垃圾 未被標記,在下一次 GC 才能清除,所以被成爲 浮動垃圾

因爲 內存回收用戶線程 是同時進行的,內存在被 回收 的同時,也在被 分配。當 老生代 中的內存使用超過必定的比例時,系統將會進行 垃圾回收;當 剩餘內存 不能知足程序運行要求時,系統將會出現 Concurrent Mode Failure,臨時採用 Serial Old 算法進行 清除,此時的 性能 將會下降。

  1. 垃圾收集結束後殘餘大量空間碎片

CMS 回收器採用的 標記清除算法,自己存在垃圾收集結束後殘餘 大量空間碎片 的缺點。CMS 配合適當的 內存整理策略,在必定程度上能夠解決這個問題。

5.2. G1回收器(垃圾區域Region優先)

G1JDK 1.7 中正式投入使用的用於取代 CMS壓縮回收器。它雖然沒有在物理上隔斷 新生代老生代,可是仍然屬於 分代垃圾回收器G1 仍然會區分 年輕代老年代,年輕代依然分有 Eden 區與 Survivor 區。

G1 首先將 分爲 大小相等Region,避免 全區域 的垃圾回收。而後追蹤每一個 Region 垃圾 堆積的價值大小,在後臺維護一個 優先列表,根據容許的回收時間優先回收價值最大的 Region。同時 G1採用 Remembered Set 來存放 Region 之間的 對象引用 ,其餘回收器中的 新生代老年代 之間的對象引用,從而避免 全堆掃描G1 的分區示例以下圖所示:

這種使用 Region 劃分 內存空間 以及有 優先級 的區域回收方式,保證 G1 回收器在有限的時間內能夠得到儘量 高的回收效率

G1CMS 運做過程有不少類似之處,整個過程也分爲 4 個步驟:

  1. 初始標記(CMS initial mark)

初始標記 僅僅是標記 GC Roots直接關聯 的對象。這個階段 速度很快,須要 Stop the World

  1. 併發標記(CMS concurrent mark)

併發標記 進行的是 GC Tracing,從 GC Roots 開始對堆進行 可達性分析,找出 存活對象

  1. 從新標記(CMS remark)

從新標記 階段爲了 修正 併發期間因爲 用戶進行運做 致使的 標記變更 的那一部分對象的 標記記錄。這個階段的 停頓時間 通常會比 初始標記階段 稍長一些,但遠比 併發標記 的時間短,也須要 Stop The World

  1. 篩選回收

首先對各個 Region回收價值成本 進行排序,根據用戶所指望的 GC 停頓時間 來制定回收計劃。這個階段能夠與用戶程序一塊兒 併發執行,可是由於只回收一部分 Region,時間是用戶可控制的,並且停頓 用戶線程 將大幅提升回收效率。

與其它 GC 回收相比,G1 具有以下 4 個特色:

  • 並行與併發

使用多個 CPU 來縮短 Stop-the-World停頓時間,部分其餘回收器須要停頓 Java 線程執行的 GC 動做,G1 回收器仍然能夠經過 併發的方式Java 程序繼續執行。

  • 分代回收

與其餘回收器同樣,分代概念G1 中依然得以保留。雖然 G1 能夠不須要 其餘回收器配合 就能獨立管理 整個GC堆,但它可以採用 不一樣的策略 去處理 新建立的對象已經存活 一段時間、熬過屢次 GC 的舊對象,以獲取更好的回收效果。新生代老年代 再也不是 物理隔離,是多個 大小相等 的獨立 Region

  • 空間整合

CMS標記—清理 算法不一樣,G1總體 來看是基於 標記—整理 算法實現的回收器。從 局部(兩個 Region 之間)上來看是基於 複製算法 實現的。

但不管如何,這 兩種算法 都意味着 G1 運做期間 不會產生內存空間碎片,回收後能提供規整的可用內存。這種特性有利於程序長時間運行,分配大對象 時不會由於沒法找到 連續內存空間 而提早觸發 下一次 GC

  • 可預測的停頓

這是 G1 相對於 CMS 的另外一大優點,下降停頓時間G1CMS 共同的關注點。G1 除了追求 低停頓 外,還能創建 可預測停頓時間模型,能讓使用者明確指定在一個 長度M 毫秒的 時間片斷 內,消耗在 垃圾回收 上的時間不得超過 N 毫秒。(後臺維護的 優先列表,優先回收 價值大Region)。

參考

周志明,深刻理解Java虛擬機:JVM高級特性與最佳實踐,機械工業出版社


歡迎關注技術公衆號:零壹技術棧

零壹技術棧

本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。

相關文章
相關標籤/搜索