天天看點

Java用什麼清理垃圾?

作者:IT技術控

垃圾回收器介紹

有 8 種不同的垃圾回收器,它們分别用于不同分代的垃圾清理

  • 新生代(複制算法):Serial,ParNew,Parallel Scavenge
  • 老年代(标記-清除、标記-整理):SerialOld,Parallel Old,CMS
  • 整堆:G1,ZGC

下圖是各種垃圾回收器之間的關系,連線表示互相可以配合使用。

Java用什麼清理垃圾?
收集器 特點 介紹
Serial

新生代收集器

串行

标記-複制

在回收時,JVM會暫停所有使用者線程,執行垃圾回收操作,直到操作完成後再繼續使用者線程。由于該垃圾回收器隻使用一個線程,是以也被稱為串行垃圾回收器。
Serial Old

老年代

串行

标記-整理

是Serial收集器的更新版,用于回收老年代記憶體。該垃圾回收器也隻使用一個線程進行回收。
ParNew

新生代

多線程并行執行

“标記-複制”算法

是一種并行垃圾回收器,主要用于新生代記憶體的回收。該垃圾回收器使用多線程并行執行“标記-複制”算法。
Parallel Scavenge

新生代

多線程并行執行

“标記-複制”算法

是一種并行垃圾回收器,主要用于新生代記憶體的回收。它優化了垃圾回收的吞吐量,适用于那些重視系統吞吐量的應用場景。該垃圾回收器使用多線程并行執行“标記-複制”算法,并且可以動态調整回收時間,以适應不同的記憶體負載。
Parallel Old

使用多線程并行執行“标記-整理”算法

老年代

是一種并行垃圾回收器,用于回收老年代記憶體。該垃圾回收器使用多線程并行執行“标記-整理”算法。

CMS

Concurrent Mark Sweep)

使用多線程并行執行,低延時,減少STW對使用者的影響

“标記-清除”算法

老年代

是一種混合垃圾回收器,主要用于回收老年代記憶體。該垃圾回收器使用多線程并行執行“标記-清除”算法,其中标記階段是并發執行的,即在不停頓使用者線程的情況下标記可回收對象,而清除階段則會暫停使用者線程。首次實作了讓垃圾收集線程與使用者線程(基本上)同時工作(并發收集)

G1

Garbage-First

使用多線程并行執行“标記-整理”算法

吞吐量和低延時都行的整堆垃圾收集器

可預測停頓

是一個橫跨新生代和老年代的垃圾回收器。它打亂了以前的堆結構,直接将堆分成極其多個區域。每個區域都可以充當 Eden 區、Survivor 區或者老年代中的一個。它采用的是标記 - 壓縮算法,而且和 CMS 一樣都能夠在應用程式運作過程中并發地進行垃圾回收。G1 能夠針對每個細分的區域來進行垃圾回收。在選擇進行垃圾回收的區域時,它會優先回收死亡對象較多的區域。這也是 G1 名字的由來。

zgc

Z Garbage Collector

使用多線程并行執行“标記-複制”算法

整堆

是一種可伸縮低延遲的垃圾回收器,在大多數情況下,垃圾收集的停頓時間不到10毫秒。與CMS中的ParNew和G1類似,ZGC也采用标記-複制算法,不過ZGC對該算法做了重大改進:ZGC在标記、轉移和重定位階段幾乎都是并發的,這是ZGC實作停頓時間小于10ms目标的最關鍵原因。ZGC的設計思想是将Java堆分成一些小塊,每個塊都可以獨立地進行垃圾回收。這樣,在垃圾收集過程中,隻需要回收一小部分的Java堆,進而實作短暫的停頓時間。同時,ZGC還支援動态地調整Java堆的大小,以便更好地适應應用程式的需求。

串行垃圾回收器Serial、SerialOld

單線程收集器,發展曆史最悠久的收集器,當它在進行垃圾收集工作的時候,其他線程都必須暫停直到垃圾收集結束(Stop The World)。

雖然Serial收集器存在Stop The World的問題,但是在并行能力較弱的單CPU環境下往往表現優于其他收集器;因為它簡單而高效,沒有多餘的線程互動開銷;Serial對于運作在Client模式下的虛拟機來說是個很好的選擇

使用-XX:+UseSerialGC參數可以設定這個Serial收集器

特點:

  • Serial新生代收集器,單線程執行,使用複制算法
  • SerialOld老年代收集器,單線程執行,使用标記-整理算法
  • 進行垃圾收集時,必須暫停使用者線程
Java用什麼清理垃圾?

Parallel Scavenge

Parallel Scavenge收集器是采用複制算法的多線程新生代收集器,它與其他的收集器的不同之處在于它主要關心的是吞吐量,而其他的收集器關注的是盡可能的減少使用者線程的等待時間(縮短Stop The World的時間)。

吞吐量=使用者線程執行時間/(使用者線程執行時間+垃圾收集時間),虛拟機總共運作100分鐘,其中垃圾收集花費時間1分鐘,那麼吞吐量就是 99%

停頓時間越短适合需要和使用者進行互動的程式,良好的響應能夠提升使用者的體驗。而高效的吞吐量可以充分的利用CPU時間,盡快的完成計算任務,是以Parallel Scavenge收集器适用于背景計算型任務程式。

-XX:+UseParallelGC設定使用該回收器

特點:

  • 吞吐量優先收集器,垃圾收集需要暫停使用者線程
  • 新生代使用并行回收器Parallel Scavenge,采用複制算法
  • 老年代使用串行收集器Serial Old,采用标記-整理算法
Java用什麼清理垃圾?

Parallel Old

Parallel Old收集器可以配合Parallel Scavenge收集器一起使用達到“吞吐量優先”,它主要是針對老年代的收集器,使用的是标記-整理算法。在注重吞吐量的任務中可以優先考慮使用這個組合。自 JDK 9 開始,Parallel Old 垃圾回收器已被 G1(Garbage-First)垃圾回收器取代為預設老年代回收器。

适用于吞吐量優先的應用場景,例如批處理任務、資料處理任務等。

-XX:+UseParallelOldGc 設定老年代使用該回收器。

XX:+ParallelGCThreads 設定垃圾收集時的線程數量。

特點:

  • PS收集器的老年代版本
  • 吞吐量優先收集器,垃圾收集需要暫停使用者線程,對CPU敏感
  • 老年代使用并行收集器Parallel Old,采用标記-整理算法
  • 新生代使用并行回收器Parallel Scavenge,采用複制算法
Java用什麼清理垃圾?

ParNew

ParNew收集器是Serial收集器的多線程版本;除了使用了多線程進行垃圾收集以外,其他的都和Serial一緻;它預設開始的線程數與CPU的核數相同,可以通過參數-XX:ParallelGCThreads來設定線程數。

從最上面的圖可以看出,能夠與CMS配合使用的收集器,除了Serial以外,就隻剩下ParNew,是以ParNew通常是運作在Server模式下的首選新生代垃圾收集器

使用·-XX:+UseParNewGC·參數可以設定新生代使用這個并行回收器

特點:

  • 新生代采用并行回收器ParNew,Serial的多線程版。
  • 老年代采用串行回收器SerialOld
  • 單核CPU不建議使用
Java用什麼清理垃圾?

CMS

cms介紹

CMS收集器是一種以擷取最短回收停頓時間為目标的收集器,在網際網路網站、B/S架構的中常用的收集器就是CMS,因為系統停頓的時間最短,給使用者帶來較好的體驗。JDK 9 及以後的版本中已被标記為廢棄(deprecated),JDK 14被移除。

适用于響應時間優先的應用場景,例如 Web 伺服器、實時系統等,它們對于長時間的停頓非常敏感。

-XX:+UseConcMarkSweepGC設定老年代使用該回收器。

-XX:ConcGCThreads設定并發線程數量。

新生代預設ParNew 老年代:CMS

特點:

  • 低延時,減少STW對使用者的影響
  • 并發收集,使用者線程與收集線程一起執行,對CPU資源敏感
  • 不會等待堆填滿再收集,到達門檻值就開始收集。
  • 采用标記-清除算法,是以會産生記憶體碎片

gc過程

Java用什麼清理垃圾?
  • 01-初始标記階段:會STW,标記出GCRoots可以關聯到的對象,關聯對象較少,是以很快
  • 02-并發标記階段:不會STW,周遊GCRoots直接對象的引用鍊,耗時長
  • 03-重新标記階段:會STW,修正并發标記期間的新對象記錄
  • 04-并發清除階段:不會STW,清除垃圾對象,釋放記憶體空間

優缺點

  • 優點 CMS是一款優秀的收集器,它的主要優點:并發收集、低停頓,是以CMS收集器也被稱為并發低停頓收集器(Concurrent Low Pause Collector)。
  • 缺點
    • CMS收集器對CPU資源非常敏感。 在并發階段,它雖然不會導緻使用者線程停頓,但會因為占用了一部分線程(或者說CPU資源)而導緻應用程式變慢,總吞吐量會降低。CMS預設啟動的回收線程數是(CPU數量+3)/4,也就是當CPU在4個以上時,并發回收時垃圾收集線程不少于25%的CPU資源,并且随着CPU數量的增加而下降。但是當CPU不足4個時(比如2個),CMS對使用者程式的影響就可能變得很大,如果本來CPU負載就比較大,還要分出一半的運算能力去執行收集器線程,就可能導緻使用者程式的執行速度忽然降低了50%,其實也讓人無法接受。
    • 無法處理浮動垃圾。 由于CMS并發清理階段使用者線程還在運作着,伴随程式運作自然就還會有新的垃圾不斷産生。這一部分垃圾出現在标記過程之後,CMS無法再當次收集中處理掉它們,隻好留待下一次GC時再清理掉。這一部分垃圾就被稱為“浮動垃圾”。也是由于在垃圾收集階段使用者線程還需要運作,那也就還需要預留有足夠的記憶體空間給使用者線程使用,是以CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,回收閥值可以通過參數 -XX:CMSInitiatingoccupancyFraction來設定;如果回收閥值設定的太大,在CMS運作期間如果配置設定大的對象找不到足夠的空間就會出現“Concurrent Mode Failure”失敗,這時候會臨時啟動SerialOld GC來重新進行老年代的收集,這樣的話停頓的時間就會加長。
    • 标記-清除算法導緻的空間碎片 CMS是一款基于“标記-清除”算法實作的收集器,這意味着收集結束時會有大量空間碎片産生。空間碎片過多時,将會給大對象配置設定帶來很大問題,往往出現老年代空間剩餘,但無法找到足夠大連續空間來配置設定目前對象。為了解決這個問題CMS提供了一個參數 -XX:+UseCMSCompactAtFullCollecion,如果啟用,在Full GC的時候開啟記憶體碎片整理合并過程,由于記憶體碎片整理的過程無法并行執行,是以停頓的時間會加長。考慮到每次FullGC都要進行記憶體碎片合并不是很合适,是以CMS又提供了另一個參數 -XX:CMSFullGCsBeforeCompaction來控制執行多少次不帶碎片整理的FullGC之後,來一次帶碎片整理GC

G1

G1介紹

G1是一款面向服務端應用的全功能型垃圾收集器,大記憶體企業配置的主要是G1。

G1 算法取消了堆中年輕代與老年代的實體劃分,但它仍然屬于分代收集器。G1 算法将堆劃分為若幹個區域,稱作 Region,如下圖中的小方格所示。一部分區域用作年輕代,一部分用作老年代,另外還有一種專門用來存儲巨型對象的分區。

Java用什麼清理垃圾?
  • 特點:
    • G1 垃圾回收器使用的是分代垃圾回收算法,在執行垃圾回收時,會優先選擇垃圾最多的區域進行回收(Garbage-First),以達到最大化回收效率的目标。
    • 全局使用标記-整理算法收集,局部采用複制算法收集
    • G1 垃圾回收器通過分代回收和智能化的區域選擇,可以在大堆記憶體下降低停頓時間。能讓使用者指定GC消耗時間,預設是200ms。
    • G1最大堆記憶體32M2048=64GB,最小堆記憶體1M2048=2GB,低于此值不建議使用
    • G1 垃圾回收器适用于中大型堆記憶體,具有較高吞吐量需求,同時對停頓時間有一定容忍度的應用場景。它适合于大多數的伺服器應用和後端服務。
  • 配置參數:-XX:+UseG1GC
  • 配置說明

GC過程

Java用什麼清理垃圾?
  • G1 的年輕代回收,采用複制算法,并行進行收集,收集過程會 STW。
  • G1 的老年代回收時也同時會對年輕代進行回收。主要分為四個階段: 初始标記階段:完成對根對象的标記,這個過程是STW的;标記出GCRoots可以關聯到的對象,耗時短。 并發标記階段:不會STW,這個階段是和使用者線程并行執行的;周遊GCRoots直接對象的引用鍊,耗時長。 最終标記階段:會STW,完成三色标記周期,修正并發标記期間,标記産生變動的那部分。 複制/清除階段:這個階段會優先對可回收空間較大的 Region 進行回收,即 garbage first,這也是 G1 名稱的由來。

G1 也和 CMS 一樣會周遊全部的對象,然後标記對象引用情況,在清除對象後會對區域進行複制移動整合碎片空間。

G1 采用每次隻清理一部分而不是全部的 Region 的增量式清理,由此來保證每次 GC 停頓時間不會過長。

标記階段停頓分析

  • 初始标記階段:初始标記階段是指從GC Roots出發标記全部直接子節點的過程,該階段是STW的。由于GC Roots數量不多,通常該階段耗時非常短。
  • 并發标記階段:并發标記階段是指從GC Roots開始對堆中對象進行可達性分析,找出存活對象。該階段是并發的,即應用線程和GC線程可以同時活動。并發标記耗時相對長很多,但因為不是STW,是以我們不太關心該階段耗時的長短。
  • 再标記階段:重新标記那些在并發标記階段發生變化的對象。該階段是STW的。

清理階段停頓分析

  • 清理階段清點出有存活對象的分區和沒有存活對象的分區,該階段不會清理垃圾對象,也不會執行存活對象的複制。該階段是STW的。

複制階段停頓分析

  • 複制算法中的轉移階段需要配置設定新記憶體和複制對象的成員變量。轉移階段是STW的,其中記憶體配置設定通常耗時非常短,但對象成員變量的複制耗時有可能較長,這是因為複制耗時與存活對象數量與對象複雜度成正比。對象越複雜,複制耗時越長。

ZGC

ZGC介紹

ZGC ( Z Garbage Collector )在 JDK11中引入的一種可擴充的低延遲垃圾收集器,在 JDK15中釋出穩定版。

ZGC 針對大堆記憶體設計可以支援 TB 級别的堆,ZGC 非常高效,能夠做到 10ms 以下的回收停頓時間,JDK16釋出後,GC停頓時間已經縮小到1ms以内。

  • 配置參數 shell複制代碼-XX:+UseZGC #啟用ZGC -Xmx # 設定最大堆記憶體 -xlog:gc # 列印 GC日志 -Xlog:gc* # 列印 GC 詳細日志

特點:

  • ZGC 垃圾回收器使用的是整堆垃圾回收算法。它的設計目标是在非常大的堆記憶體下實作低延遲的垃圾回收。ZGC 将整個堆記憶體作為一個整體來進行回收
  • 采用标記-整理算法
  • ZGC 垃圾回收器的設計目标是實作非常低的停頓時間,通常控制在幾毫秒到幾十毫秒之間,無論堆記憶體大小。這使得 ZGC 更适合對延遲要求極高的應用場景,例如實時系統和互動性應用。
  • 适合記憶體8MB~16TB
  • ZGC 垃圾回收器适用于非常大的堆記憶體和對延遲敏感的應用場景,特别是那些需要低停頓時間的實時應用或互動性應用。

zgc 記憶體分布

跟 G1 類似,ZGC 的堆記憶體也是基于 Region 來分布,不過 ZGC 是不區分新生代老年代的。不同的是,ZGC 的 Region 支援動态地建立和銷毀,并且 Region 的大小不是固定的,包括三種類型的 Region

Java用什麼清理垃圾?
  • Small Region:2MB,主要用于放置小于 256 KB 的小對象。
  • Medium Region:32MB,主要用于放置大于等于 256 KB 小于 4 MB 的對象。
  • Large Region:N * 2MB。這個類型的 Region 是可以動态變化的,不過必須是 2MB 的整數倍,最小支援 4 MB。每個 Large Region 隻放置一個大對象,并且是不會被重配置設定的。

zgc關鍵技術

這麼快的響應,ZGC 是如何做到的呢?這是由于 ZGC 具有以下特點。

  • ZGC 使用了着色指針技術,我們知道 64 位平台上,一個指針的可用位是 64 位,ZGC 限制最大支援 4TB 的堆,這樣尋址隻需要使用 42 位,那麼剩下 22 位就可以用來儲存額外的資訊,着色指針技術就是利用指針的額外資訊位,在指針上對對象做着色标記。
  • 第二個特點是使用讀屏障,ZGC 使用讀屏障來解決 GC 線程和應用線程可能并發修改對象狀态的問題,而不是簡單粗暴的通過 STW 來進行全局的鎖定。使用讀屏障隻會在單個對象的處理上有機率被減速。
  • 由于讀屏障的作用,進行垃圾回收的大部分時候都是不需要 STW 的,是以 ZGC 的大部分時間都是并發處理,也就是 ZGC 的第三個特點。
  • 第四個特點是基于 Region,這與 G1 算法一樣,不過雖然也分了 Region,但是并沒有進行分代。ZGC 的 Region 不像 G1 那樣是固定大小,而是動态地決定 Region 的大小,Region 可以動态建立和銷毀。這樣可以更好的對大對象進行配置設定管理。
  • 第五個特點是壓縮整理。CMS 算法清理對象時原地回收,會存在記憶體碎片問題。ZGC 和 G1 一樣,也會在回收後對 Region 中的對象進行移動合并,解決了碎片問題。
讀屏障是JVM向應用代碼插入一小段代碼的技術。當應用線程從堆中讀取對象引用時,就會執行這段代碼。需要注意的是,僅“從堆中讀取對象引用”才會觸發這段代碼。

zgc過程

Java用什麼清理垃圾?
  • 開始進行回收時,ZGC 首先會進行一個短暫的 STW,來進行 roots 标記。這個步驟非常短,因為 roots 的總數通常比較小。
  • 然後就開始進行并發标記,如上圖所示,通過對對象指針進行着色來進行标記,結合讀屏障解決單個對象的并發問題。其實,這個階段在最後還是會有一個非常短的 STW 停頓,用來處理一些邊緣情況,這個階段絕大部分時間是并發進行的,是以沒有明顯标出這個停頓。
  • 下一個是清理階段,這個階段會把标記為不在使用的對象進行回收,如上圖所示,把橘色的不在使用的對象進行了回收。
  • 最後一個階段是重定位,重定位就是對 GC 後存活的對象進行移動,來釋放大塊的記憶體空間,解決碎片問題。
  • 重定位最開始會有一個短暫的 STW,用來重定位集合中的 root 對象。暫停時間取決于 root 的數量、重定位集與對象的總活動集的比率。
  • 最後是并發重定位,這個過程也是通過讀屏障,與應用線程并發進行的

繼續閱讀