有它的應用場景,比如你可 能需要為你的項目選擇合適的垃圾收集器(一般情況下不會),那麼就需要對各種垃圾收集器有一個整體的瞭解。就是看了《深入理解 Java 虛擬機》一書,發現“垃圾收集器”在實際項目中還
在講解具體收集器前,需先明確幾個關鍵維度(這是區分收集器的核心):
Java 虛擬機的垃圾回收器許可分為四大類別:串行收集器、並行收集器、CMS 收集器、G1 收集器在 HotSpot 虛擬機裏面實現了七種作用於不同分代的收集器。
如果兩個收集器之間存在連線,就説明它們能夠搭配使用 ,圖中收集器所處的區域,則表示它是屬於 新生代收集器抑或是老年代收集器。
Serial 收集器
Serial 收集器是最基礎、歷史最悠久的收集器,是一個單線程工作的收集器。使用 Serial 收集器,無論是進行 Minor gc 還是 Full GC ,清理堆空間時,所有的應用線程都會被暫停。進行Full GC 時,它還會對老年代空間的對象進行壓縮整理。通過 -XX:+UseSerialgGC 標誌可以啓用 Serial 收集器。
對於單核處理器或處理器核心數較少的環境來説,Serial 收集器由於沒有線程交互的開銷,專心做垃圾收集自然允許獲得最高的單線程收集效率。Serial 收集器對於運行在客户端模式下的虛擬機來説是一個很好的選擇。
- 原理:單線程執行新生代回收,全程 STW(暫停所有用户線程)。工作流程:Eden 區滿時觸發 GC → 複製 Eden+S0 存活對象到 S1 → 清空 Eden+S0 → 交換 S0/S1 角色。
- 特點:✅ 優點:實現簡便、無線程切換開銷,內存佔用小;❌ 缺點:單線程效率低,STW 時間長(堆內存越大,停頓越久)。
- 適用場景:客户端程序(如桌面應用)、小內存場景(堆 < 1G)、單核 CPU 環境。
- 參數配置:
-XX:+UseSerialGC(啓用 Serial+Serial Old 組合)。
ParNew 收集器
ParNew 收集器實質上是 Serial 收集器的多線程並行版本。除了同時使用多條線程進行垃圾收集之外,其餘的行為包括 Serial 收集器可用的所有控制參數、收集算法、Stop The World、 對象分配規則、回收策略等都與 Serial 收集器完全一致。
首選的新生代收集器。
- 原理用多線程執行復制操作。工作流程:與 Serial 相同,僅回收線程數可配置(默認等於 CPU 核心數)。就是:Serial 的多線程版本,核心邏輯與 Serial 一致,只
- 特點:✅ 優點:多線程提升新生代回收效率,STW 時間短於 Serial;❌ 缺點:線程切換有開銷,單核環境下性能不如 Serial。
- 適用場景:服務端程序(如 WEB 應用),常與 CMS 老年代收集器搭配(CMS 默認新生代用 ParNew)。
- 參數配置:
-XX:+UseParNewGC(啓用 ParNew)、-XX:ParallelGCThreads(設置回收線程數,如-XX:ParallelGCThreads=4)。
Parallel Scavenge 收集器
Parallel Scavenge 收集器也是一款新生代收集器,基於標記——複製算法實現,能夠並行 收集的多線程收集器和 ParNew 很相似。
99%。就是Parallel Scavenge 收集器的目標則是達到一個可控制的吞吐量(Throughput)。所謂吞吐量 就是處理器用於運行用户代碼的時間與處理器總消耗時間的比值。如果虛擬機完成某個任務,用 户代碼加上垃圾收集總共耗費了 100 分鐘,其中垃圾收集花掉 1 分鐘,那吞吐量就
- 原理多線程複製回收,但更關注「吞吐量」(吞吐量 = 用户代碼執行時間 /(用户代碼執行時間 + GC 時間))。承受「自適應調節策略」(-XX:+UseAdaptiveSizePolicy):JVM 自動調整新生代大小、Survivor 比例、晉升年齡,無需手動調參。就是:ParNew 的「吞吐量優化版本」,同樣
- 特點:✅ 優點:吞吐量優先,適合計算密集型任務;自適應調節簡化調優;❌ 缺點:仍為獨佔式回收(全程 STW),對延遲敏感場景不友好。
- 適用場景:大數據批處理、後台任務(如報表生成)、吞吐量優先的場景。
- 參數配置:
-XX:+UseParallelGC(啓用 Parallel Scavenge+Parallel Old 組合)、-XX:MaxGCPauseMillis(目標最大 STW 時間,JVM 會嘗試達標)、-XX:GCTimeRatio(吞吐量比例,如-XX:GCTimeRatio=99表示 GC 時間佔比≤1%)。
Serial Old 收集器
Serial Old 是 Serial 收集器的老年代版本,它同樣是一個單線程收集器,使用 “標記-整理 算法”。
- 原理:Serial 的老年代版本,單線程執行「標記 - 整理」算法(標記存活對象→將存活對象向內存一端移動→清除末端無用內存)。全程 STW。
- 特點:與 Serial 一致,簡單但效率低。
- 適用場景:客户端程序、小內存場景,或作為 CMS 收集器的「後備方案」(CMS 併發失敗時,Serial Old 會接管老年代回收)。
- 參數配置:配合
-XX:+UseSerialGC啓用。
Parallel Old 收集器
Parallel Old 是 Parallel Scavenge 收集器的老年代版本,支撐多線程併發收集,基於標記 -整理算法實現。
使用 java -XX:+PrintCommandLineFlags -version 命令可以查看
- 原理:Parallel Scavenge 的老年代版本,多線程執行「標記 - 整理」算法,全程 STW。與 Parallel Scavenge 搭配,形成「新生代並行 + 老年代並行」的全並行回收組合。
- 特點:吞吐量高,STW 時間短於 Serial Old,但仍為獨佔式回收。
- 適用場景:與 Parallel Scavenge 配套,用於吞吐量優先的場景(如批處理)。
- 參數配置:配合
-XX:+UseParallelGC啓用,或單獨用-XX:+UseParallelOldGC。
CMS 收集器
CMS 收集器設計的初衷是為了消除 Parallel 收集器和 Serial 收集器 Full gc 週期中的長時間停頓。CMS 收集器在 Minor gc 時會暫停所有的應用線程,並以多線程的方式進行垃圾回收。CMS 收集器基於標記-清除算法實現的,整個過程分為四個步驟, 整個過程中耗時最長的併發標記和併發清除階段中,垃圾收集器線程都可以與用户線程一起工作,所以從總體上來説,CMS 收集器的內存回收過程是與用户線程一起併發執行的。
垃圾回收過程如下:
- 初始標記(CMS initial mark):初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快; 仍然需要“Stop The World”。
- 併發標記(CMS concurrent mark):併發標記階段就是從GC Roots的直接關聯對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不得停頓用户線程,能夠與垃圾收集線程一起併發運行。
- 重新標記(CMS remark):重新標記階段則是為了修正併發標記期間,因為用户程序繼續運作而導致 標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但 也遠比並發標記階段的時間短。
- 併發清除(CMS concurrent sweep):併發清除階段,清理刪除掉標記階段判斷的已經死亡的對象, 由於不需要移動存活對象,所以該階段也是可能與用户線程同時併發的。
一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的 Java 應用集中在互聯網網站或者基於瀏覽器的 B/S 系統的服務端上,這類應用通常都會較為關注服務的響應速度,希望系統停頓時間儘可能短,以給用户帶來良好的交互體驗。CMS 收集器就非常符合這類應用的需求。就是CMS 收集器
- 原理:基於「標記 - 清除」算法,分為 4 個階段:
- 初始標記:STW,標記 GC Roots 直接關聯的老年代對象(速度快,停頓短);
- 併發標記:與用户線程並行,遍歷標記存活對象(無 STW,但可能因用户線程修改對象導致標記不準確);
- 重新標記:STW,修正併發標記期間被修改的對象標記(停頓比初始標記長,但遠短於獨佔式回收);
- 併發清除:與用户線程並行,清除未標記的無用對象(無 STW)。
- 特點:✅ 優點:併發回收,STW 時間極短(毫秒級),適合低延遲場景;❌ 缺點:
- 內存碎片:標記 - 清除算法不整理內存,長期運行會產生碎片,導致大對象無法分配而觸發 Full GC;
- CPU 敏感:併發階段佔用 CPU 資源,CPU 核心少的場景下會降低用户程序吞吐量;
- 併發失敗:老年代內存增長過快時,CMS 來不及回收,會觸發「Concurrent Mode Failure」,轉而用 Serial Old 回收(導致長時間 STW)。
- 適用場景:WEB 服務、電商秒殺、金融交易等低延遲場景(JDK9 已廢棄,被 G1 替代)。
- 參數配置:
-XX:+UseConcMarkSweepGC(啓用 CMS+ParNew 組合)、-XX:CMSInitiatingOccupancyFraction(老年代佔用比例閾值,觸發 CMS 回收,如-XX:CMSInitiatingOccupancyFraction=75)、-XX:+UseCMSCompactAtFullCollection(Full GC 時整理內存,減少碎片)。
Garbage First 收集器(G1)
G1 回收器是 JDK 1.7 中利用的全新垃圾回收器,從長期目標來看,其是為了取代 CMS 回收器。
G1 回收器擁有獨特的垃圾回收策略,和之前所有垃圾回收器採用的垃圾回收策略不同。從分代看,G1 依然屬於分代垃圾回收器。但它最大的改變是使用了分區算法,從而使得 Eden 區、 From 區、Survivor 區和老年代等各塊內存不必連續。
在 G1 回收器之前,所有的垃圾回收器其內存分配都是連續的一塊內存,如下圖所示。
而在 G1 回收器中,其將一大塊的內存分為許多細小的區塊,從而不要求內存是連續的。
從上圖可以看到,每個 Region 被標記了 E、S、O 和 H,説明每個 Region 在運行時都充當了一種角色。所有標記為 E 的都是 Eden 區的內存,它們散落在內存的各個角落,並不要求內存連續。同理,Survivor 區、老年代(Old)也是如此。
巨型對象(humongous object,H-obj),當新建對象大小超過 Region 大小一半時,直接在新的一個或多個連續 Region 中分配,並標記為 H。就是從上圖我們還可以看到 H 是以往算法中沒有的,它代表 Humongous。這表示這些 Region 存儲的
堆內存中一個 Region 的大小可以通過 -XX:G1HeapRegionSize 參數指定,大小區間只能是 1M、2M、4M、8M、16M 和 32M,總之是 2 的冪次方。要是 G1HeapRegionSize為 默認值,即把設置的最小堆內存按照 2048 份均分,最後得到一個合理的大小。
G1 收集器的收集過程主要有四個階段:
- 新生代 GC
- 併發標記週期
- 混合收集
- 假設需要,可能進行 FullGC
清空 Eden 區,將存活對象移動到 Survivor 區,部分年齡到了就移動到老年代。就是新生代 GC 與其他垃圾收集器的類似,就
獨佔式的,會引起停頓。並且初始標記會引發一次新生代 GC。在這個階段,所有將要被回收的區域會被 G1 記錄在一個稱之為 Collection Set 的 集合中。就是併發標記週期則分為:初始標記、根區域掃描、併發標記、重新標記、獨佔清理、併發清理階段。其中初始標記、重新標記、獨佔清理
混合回收階段會首先針對 Collection Set 中的內存進行回收,因為這些垃圾比例較高。G1 回收器的名字 Garbage First 就是這個意思,垃圾優先處理的意思。在混合回收的時候,也會執行多次新生代 GC 和 混合 GC,從而來進行內存的回收。
必要時進行 Full GC。當在回收階段遇到內存不足時,G1 會停止垃圾回收並進行一次 Full GC,從而騰出更多空間進行垃圾回收。
相關參數
打開 G1 收集器,我們可以使用參數:-XX:+UseG1GC。
設置目標最大停頓時間,行使用參數:-XX:MaxGCPauseMillis。
設置 GC 工作線程數量,可以使用參數:-XX:ParallelGCThreads。
設置堆使用率觸發併發標記週期的執行,可以應用參數:-XX:InitiatingHeapOccupancyPercent。
通過從一開始的串行回收器,到後來的並行回收器、CMS 回收器,到最後的 G1 回收器,垃圾回收器不斷改進,使得垃圾回收效率不斷提升。專門是分區思想誕生後,對於垃圾回收停頓時間的控制更加細膩,能夠讓應用有更完美的延時控制,從而呈現更好的用户體驗。
- 原理:
- 分區管理:將堆內存劃分為多個大小相等的 Region(1M~32M),每個 Region 可動態充當 Eden/Survivor/Old 區(打破固定分代);
- Remembered Set:每個 Region 維護 Remembered Set(記錄跨 Region 引用),避免全堆掃描;
- 回收流程:
- Young GC:回收 Eden+Survivor Region,複製存活對象到新 Region(STW,多線程);
- 混合回收(Mixed GC):老年代 Region 佔用達閾值時觸發,回收 Eden+Survivor + 部分老年代 Region(部分階段併發);
- Full GC:G1 回退方案(如內存不足時),採用標記 - 整理算法(STW,應儘量避免)。
- 特點:✅ 優點:
- 兼顧吞吐量與延遲,可設置最大 STW 時間(
-XX:MaxGCPauseMillis); - 分區回收減少碎片,無需單獨整理內存;
- 適配大內存(4G~16G)場景;❌ 缺點:
- Remembered Set 佔用額外內存(約堆內存的 5%~10%);
- 小內存場景下性能不如 Parallel GC。
- 適用場景:中等堆內存(4G~16G)、混合業務場景(兼顧吞吐量與延遲)。
- 參數配置:
-XX:+UseG1GC(啓用 G1)、-XX:MaxGCPauseMillis(目標最大 STW 時間,如-XX:MaxGCPauseMillis=200)、-XX:G1HeapRegionSize(Region 大小,默認根據堆內存自動分配)、-XX:InitiatingHeapOccupancyPercent(堆佔用閾值,觸發混合回收,默認 45%)。
ZGC
ZGC(Z Garbage Collector)是一款性能比 G1更加優秀的垃圾收集器。ZGC 第一次出 現是在JDK 11中以實驗性的特性引入,這也是JDK 11中最大的亮點。在JDK 15 中 ZGC通過不 再是實驗功能,能夠正式投入生產應用了。
目標低延遲
- 保證最大停頓時間在幾毫秒之內,不管你堆多大或者存活的對象有多少。
- 可以處理 8MB-16TB 的堆
通過以上歷代垃圾回收器的講解,我們大致瞭解到減少延遲的底層思想不外乎將stop the world進行極限壓縮,將能並行的部分全部採用和用户線程並行的方式處理,然而ZGC更"過分"它甚至把一分部垃圾回收的工作交給了用户線程去做,那麼它是怎麼做到的呢?ZGC 的標記和清理工作同CMS、G1大致差不多,仔細看下圖的過程,和CMS 特別像,CMS其實並沒有真正被拋棄,它的部分思想在ZGC 有發揚。
ZGC 的步驟大致可分為三大階段分別是標記、轉移、重定位。
標記:從根開始標記所有存活對象
轉移:選擇部分活躍對象轉移到新的內存空間上
重定位:因為對象地址變了,所以之前指向老對象的指針都要換到新對象地址上。
並且這三個階段都是併發的。
初始轉移需要掃描 GC Roots 直接引用的對象並進行轉移,這個過程需要 STW, STW 時間跟 GC Roots 成正比。
併發轉移準備 :分析最有回收價值 GC 分頁(無 STW) 初始轉移應對初始標記的信息
併發轉移應對併發標記的數據
標記清理過程繼承了 CMS 和 G1 的思想
ZGC 優劣
ZGC 在戰略上沿用了上幾代 GC 的算法策略,採用併發標記,併發清理的思路,在戰術上, 通過染色指針、多重映射,讀屏障等優化達到更理想的併發清理,藉助支持 NUMA 達到了更快的內存操作。但 ZGC 同樣不是銀彈,它也有自身的優缺點,如下:
優勢:
- 一旦某個 Region 的存活對象被移走之後,該 Region 立即就能夠被釋放和重用掉,而不必等待整個堆中所有指向該 Region 的引用都被修正後才能清理,這使得理論上只要還有一個空閒 Region,ZGC 就能完成收集。
- 顏色指針可能大幅減少在垃圾收集過程中內存屏障的應用數量,ZGC 只使用了讀屏障。
- 通過顏色指針具備強大的擴展性,它能夠作為一種可擴展的存儲結構用來記錄更多與對象標記、 重定位過程相關的素材,以便日後進一步提高性能。
劣勢:
- 它能承受的對象分配速率不會太高
ZGC 準備要對一個很大的堆做一次完整的併發收集。在這段時間裏面,由於應用的對象分配速率很高,將創造大量的新對象,這些新對象很難進入當次收集的標記範圍,通常就只能全部當作存活對象來看待——儘管其中絕大部分對象都是朝生夕滅的,這就產生了大量的浮動垃圾。若是這種高速分配持續維持的話,每一次完整的併發收集週期都會很長,回收到的內存空間持續小於期間併發產生的浮動垃圾所佔的空間,堆中剩餘可騰挪的空間就越來越小了。目前唯一的辦法就是儘可能地增加堆容量大小,獲得更多喘息的時間。
- 吞吐量低於 G1 GC
一般來説,可能會下降 5%-15%。對於堆越小,這個效應越明顯,堆非常大的時候,比如100G,其他 GC 可能一次 Major 或 Full GC 要幾十秒以上,但對於 ZGC 不需要那麼大暫停。這種細粒度的優化帶來的副作用就是,把很多環節其他 GC 裏的 STW 整體處理,拆碎了,放到了更大時間範圍內裏去跟業務線程併發執行,甚至會直接讓業務線程幫忙做一些 GC 的操作,從而降低了業務線程的處理能力。
- 核心技術:
- 着色指針:將對象標記信息存儲在指針的額外位(而非對象頭),避免修改對象頭的開銷;
- 讀屏障:訪問對象時觸發讀屏障,動態跟蹤對象引用變化,無需全程 STW;
- 分區回收:與 G1 類似,但 Region 可動態擴容 / 收縮,支持超大堆。
- 回收流程:
- 併發標記:遍歷標記存活對象(無 STW);
- 併發重分配:將存活對象複製到新 Region(部分階段 STW,毫秒級);
- 併發重映射:更新指針指向新 Region(無 STW)。
- 特點:✅ 優點:
- STW 時間極短(<10ms),幾乎可忽略;
- 支撐超大堆(TB 級),適合內存密集型應用;
- 無內存碎片(複製算法);❌ 缺點:CPU 佔用高(讀屏障、着色指針帶來開銷)。
- 適用場景:超大堆(>16G)、超低延遲場景(如實時風控、高頻交易)。
- 參數配置:
-XX:+UseZGC(啓用 ZGC)、-XX:ZHeapSize(堆大小,如-XX:ZHeapSize=32G)。
總結
其實 ZGC 並不是一個憑空冒出的全新垃圾回收,它結合前幾代 GC 的思想,同時在戰術上做了優化以達到極限的 STW,ZGC 的優秀表現有可能會改變未來應用編寫方式,站在垃圾收集器的角度,垃圾收集器獨特喜歡不可變對象,原有編程方式鑑於內存、GC 能力所限使用可變對象來複用對象而不是銷燬重建,試想如果有了 ZGC 的強大回收能力的加持,是不是我們就可以無腦的使用不可變對象進行代碼編寫。
垃圾收集器對比
HotSpot VM 中的垃圾回收器,以及適用場景:
下面是另外一個網友給出的各垃圾收集器對比:
Serial:新生代 單線程
ParNew :新生代 多線程
Pararrel Scavenge: 新生代 多線程 吞吐量
Serial Old:老年代 單線程
Pararrel Old:老年代 多線程 吞吐量
通過CMS : 老年代 多線程 標記—清除算法,能夠與用户線程同時運行
G1:能夠回收新生代、老年代,多線程,採用 Region 管理,評估垃圾最多的 Region,進行垃圾回收,用盡可能短的時間,獲得最高的回收效率