深入理解 Java 虛擬機:JVM 中的 GC 垃圾收集器總結
前言
垃圾收集器是對應垃圾收集算法的具體實現,並且不同的垃圾收集器對應不同應用場景,會有不同的組合。
因此並不存在完美的垃圾收集器,需要通過了解垃圾收集器的特性與組合,才能在調優時選擇最適合的收集器。
新生代收集器
Serial 收集器
Serial 收集器
是最基本
、發展歷史最悠久的收集器。(老古董?)
特點: 單線程的收集器
垃圾收集算法: 複製算法
優點: 簡單而高效
,對於限定單個 CPU 環境來說,由於沒有線程交互的開銷,專心做垃圾收集從而獲得最高的單線程收集效率。
缺點: 它進行垃圾收集時,必須暫停其他所有的工作線程(即 Stop The World)
,直到它收集結束。(用戶體驗很差)
應用場景: Serial
收集器對於運行在 Client 模式
下的虛擬機來說是一個很好的選擇。
組合: Serial 收集器(新生代)
+ Serial Old 收集器(老年代)
ParNew 收集器
ParNew 收集器
是 Serial 收集器
的多線程版本
,因此所有可用的參數,收集算法,Stop The World
,對象分片規則,回收策略都與 Serial 收集器一樣
特點: 多線程收集器
垃圾收集算法: 複製算法
缺點: 它進行垃圾收集時,必須暫停其他所有的工作線程(即 Stop The World)
,直到它收集結束。(用戶體驗很差)
線程數: 默認開啓的收集線程數與 CPU 數相同
VM 指令:
- -XX:+UseConcMarkSweepGC: 開啓這個參數後,默認使用
ParNew 收集器
- -XX:+UseParNewGC: 強制使用
ParNew 收集器
- -XX:ParallelGCThreads: 指定收集線程數量
組合:
ParNew 收集器(新生代)
+Serial Old 收集器(老年代)
ParNew 收集器(新生代)
+CMS 收集器(老年代)
Parallel Scavenge 收集器
Parallel Scavenge 是一個新生代收集器,又叫吞吐量優先收集器
特點: 並行的多線程收集器,
這個收集器的目標準則是達到一個可控制的吞吐量
。
停頓時間越短就越適合需要與用戶交互的程序,良好的響應速度能提升用戶體驗;
而高吞吐量能高效的利用 CPU 時間,儘快的完成程序的運行任務,主要適合在後臺運算而不需要太多交互的任務。
吞吐量 = 運行用戶代碼時間 /(運行用戶代碼時間 + 垃圾收集時間)
PS:虛擬機運行 100 分鐘,其中垃圾回收用時 1 分鐘,吞吐量就是 99%
垃圾收集算法: 複製算法
VM 指令:
- -XX:MaxGCPauseMillis: 控制最大垃圾收集停頓時間,單位毫秒
- -XX:GCTimeRatio: 直接設置吞吐量大小,該值 0 < x < 100 的整數
即最大 GC 停頓時間爲 1 / (1 + x),默認值爲 99 - -XX:+UseAdaptiveSizePolicy: 當這個參數開啓後,即開啓了 GC 自適應策略,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調節參數以適應停頓的時間或最大吞吐量
應用場景: 注重高吞吐量,CPU 資源敏感的系統,比如大數據統計計算系統
組合:
Parallel Scavenge 收集器(新生代)
+Serial Old 收集器(老年代)
JDK 1.6 之前,別無選擇Parallel Scavenge 收集器(新生代)
+Parallel Old 收集器(老年代)
JDK 1.6 開始
老年代收集器
Serial Old 收集器
Serial Old 收集器
是 Serial 收集器
的老年代版本,JDK 1.6 開始提供
特點: 單線程的收集器
垃圾收集算法: 標記-整理算法
優點: 簡單而高效
,對於限定單個 CPU 環境來說,由於沒有線程交互的開銷,專心做垃圾收集從而獲得最高的單線程收集效率。(同 Serial 收集器)
缺點: 它進行垃圾收集時,必須暫停其他所有的工作線程(即 Stop The World)
,直到它收集結束。(同 Serial 收集器)
應用場景:
- 運行在
Client 模式
下的虛擬機。(同 Serial 收集器) Server 模式下
,CMS 收集器
的後備方案,在併發發生Concurrent Mode Failure
時使用。Server 模式下
,JDK 1.5 及以前版本中與Parallel Scavenge 收集器
搭配使用。(也就是說現在不用了)
組合: Serial 收集器(新生代)
+ Serial Old 收集器(老年代)
Parallel Old 收集器
Parallel Old 收集器
是 Parallel Scavenge 收集器
的老年代版本
特點: 多線程收集器
。
垃圾收集算法: 標記-整理算法
應用場景: 注重高吞吐量,CPU 資源敏感的系統,比如大數據統計計算系統
組合: Parallel Scavenge 收集器(新生代)
+ Parallel Old 收集器(老年代)
CMS 收集器
CMS 收集器
是一種獲取最短回收停頓時間爲目標的收集器。考慮用於頂替 CMS 收集器
。
特點: 重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗
垃圾收集算法: 標記-清除算法
步驟:
- 初始標記: 標記
GC Root
能直接關聯的對象,速度很快 - 併發標記: 進行
GC Root Tracing
過程,與併發清除一同工作,耗時長 - 重新標記: 修正併發標記期間因用戶程序運行導致的標記產生變動的那一部分對象的標記記錄
- 併發清除: 垃圾回收,與併發標記一同工作,耗時長
優點: 併發收集,低停頓
。整個回收中,耗時最長的 併發標記 與 併發清除 都可以與用戶線程併發運行
缺點:
- 併發階段,雖然不會停頓用戶線程,但是會
佔用一部分 CPU 資源,導致應用程序變慢,總吞吐量降低
。默認線程數是 (CPU 數量 + 3)/ 4,當 4 個 CPU 以上時,併發回收垃圾收集線程佔用資源不小於 25%,隨 CPU 數量增加而下降。 CMS 收集器
無法處理浮動的垃圾 (標記階段後又產生的垃圾,只能放下次 GC 回收),可能出現 Concurrent Mode Failure 導致 Full GC。- 由於是併發回收,因此老年代不能等幾乎滿了再去回收,需要 預留一部分空間提供併發收集時的程序運作使用。
- Concurrent Mode Failure 失敗就會導致啓用後備方案
Serial Old 收集器
進行老年代垃圾回收,停頓更久。 - 基於
標記-清除算法
實現的回收,因此 存在大量空間碎片的產生,會給大對象分配帶來大麻煩
VM 指令:
- -XX:CMSInitiatingOccupancyFraction: 老年代使用閾值,達到後啓用 GC,設置太高可能導致 Concurrent Mode Failure,JDK 1.5 默認值爲 68%,JDK 1.6 默認值爲 92%。
- -XX:+UseCMSCompactAtFullCollection: 開關參數,默認開啓,用於在
CMS 收集器
頂不住要進行 Full GC 時,開啓內存碎片的合併整理過程。 - -XX:CMSFullGCsBeforeCompaction: 設置執行多少次不壓縮的 Full GC 後,跟着來一次帶壓縮的。(默認值爲 0,表示每次進行 Full GC 都進行碎片整理)
應用場景: Java 應用集中在互聯網站或 B/S 系統的服務端上
組合: ParNew 收集器(新生代)
+ CMS 收集器(老年代)
兩用收集器
G1 收集器
G1 收集器是一款 面向服務端應用的垃圾收集器 ,JDK 1.7 中被視爲最前沿的成果之一,JDK 1.9 默認垃圾收集器爲 G1
特點:
- 並行與併發: 充分利用 CPU 資源、多核環境下的硬件優勢,使用多 CPU 來縮短 Stop-The-World 的時間,且
G1 收集器
依然能通過併發的方式讓 Java 代碼繼續運行。 - 分代收集: 依然保留 分代收集 的思想,且無需與其他收集器配合,能夠 獨立管理整個 GC 堆。
- 空間整合: 基於
標記-整理
算法實現,不會產生空間內存碎片。 - 可預測與停頓: 能建立 可預測的停頓時間模型,使用者能明確指定在一個長度爲 M 毫秒的時間內,消耗在垃圾回收上的時間不超過 N 毫秒
Region 區域:
G1 將內存分爲多個大小相等的獨立區域 Region,雖然保留新生代,老年代的概念,但是已經不是物理上的隔離,他們都是一部分 Region(無需連續) 的集合。
G1 跟蹤各個 Region 裏面的垃圾堆積的價值大小,在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的 Region
垃圾收集算法: 標記-整理算法
步驟:
- 初始標記: 標記
GC Roots
能直接管理到的對象,並且修改 TAMS 的值,讓下一階段用戶程序併發執行時,能在正確可用的 Region 中創建對象,需要停頓線程,耗時短。 - 併發標記: 從
GC Roots
開始對堆中的對象,進行可達性分析,找出存活東西,這階段耗時較長,可與用戶程序併發執行。 - 最終標記: 修正在 併發標記 期間導致的產生變動的標記記錄,這個時間需要停頓線程,但是可並行執行。
- 篩選回收: 根據 Region 的價值和成本進行排序,執行回收計劃。
組合: G1 收集器
無需與其他收集器配合,能夠 獨立管理整個 GC 堆
各版本默認收集器
JDK1.7 默認垃圾收集器:Parallel Scavenge(新生代)+ Parallel Old(老年代)
JDK1.8 默認垃圾收集器:Parallel Scavenge(新生代)+ Parallel Old(老年代)
JDK1.9 默認垃圾收集器:G1
垃圾收集器選擇
純屬個人理解:
注重吞吐量系統:儘可能高效的利用 CPU 資源
- JDK 1.6 及以前:
ParNew 收集器(新生代)
+CMS 收集器(老年代)
- JDK 1.6 及以後:
Parallel Scavenge 收集器(新生代)
+Parallel Old 收集器(老年代)
一般 Java B/S 系統:注重快速響應,提升用戶體驗
- JDK 1.8 及以前:
ParNew 收集器(新生代)
+CMS 收集器(老年代)
- JDK 1.9 開始:
G1 收集器
常用JVM 參數
垃圾回收統計信息
- -XX:+PrintGC: 打印 GC
- -XX:+PrintGCDetails: 打印GC詳細信息
- -XX:+PrintGCTimeStamps: 打印CG發生的時間戳
- -Xloggc:filename: 指定 GC 日誌的位置
- -verbose:gc: 輸出虛擬機中GC的詳細情況
- -XX:+PrintCommandLineFlagsjvm: 參數可查看默認設置收集器類型
堆設置
- -Xms: 初始堆大小
- -Xmx: 最大堆大小
- -Xmn: 新生代大小
- -XX:NewRatio: 設置新生代和老年代的比值。如:爲3,表示年輕代與老年代比值爲1:3
- -XX:SurvivorRatio: 新生代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:爲3,表示Eden: Survivor=3:2,一個Survivor區佔整個新生代的1/5
- -XX:MaxTenuringThreshold: 設置轉入老年代的存活次數。如果是0,則直接跳過新生代進入老年代
- -XX:PermSize、-XX:MaxPermSize: 分別設置永久代最小大小與最大大小(Java8以前)
- -XX:MetaspaceSize、-XX:MaxMetaspaceSize: 分別設置元空間最小大小與最大大小(Java8以後)
收集器設置
- -XX:+UseSerialGC: 設置串行收集器
- -XX:+UseParallelGC: 設置並行收集器
- -XX:+UseParalledlOldGC: 設置並行老年代收集器
- -XX:+UseConcMarkSweepGC: 設置併發收集器
並行收集器設置
- -XX:ParallelGCThreads=n: 設置並行收集器收集時使用的CPU數。並行收集線程數。
- -XX:MaxGCPauseMillis=n: 設置並行收集最大暫停時間
- -XX:GCTimeRatio=n: 設置垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+n) 併發收集器設置
- -XX:+CMSIncrementalMode: 設置爲增量模式。適用於單CPU情況。
- -XX:ParallelGCThreads=n: 設置併發收集器新生代收集方式爲並行收集時,使用的CPU數。並行收集線程數。