天天看點

【重難點】【JVM 03】CMS、G1、ZGC【重難點】【JVM 03】CMS、G1、ZGC一、CMS二、G1三、ZGC

【重難點】【JVM 03】CMS、G1、ZGC

文章目錄

  • 【重難點】【JVM 03】CMS、G1、ZGC
  • 一、CMS
    • 1.介紹
    • 2.優點
    • 3.缺點
  • 二、G1
    • 1.介紹
    • 2.優勢
    • 3.應用場景
    • 4.Region 介紹
    • 5.G1 垃圾回收過程
  • 三、ZGC
    • 1.介紹
    • 2.優勢
    • 3.缺點
    • 4.ZGC 的布局
    • 5.ZGC 的特性
    • 6.ZGC 的運作過程

一、CMS

1.介紹

在 JDK 1.5 時期,HotSpot 推出了一款在強互動應用中具有劃時代意義的垃圾收集器:CMS(Concurrent - Mark - Sweep)收集器,這款收集器是 HotSpot 中第一款真正意義上的并發收集器,它第一次實作了讓垃圾收集線程與使用者線程同時工作

CMS 收集器的關注點是盡可能縮短垃圾收集時使用者線程的停頓時間,停頓時間越短(低延遲)就越适合那些與使用者互動的程式

CMS 的垃圾收集算法采用标記 - 清除,并且也會 “Stop - The - World”

不幸的是,CMS 作為老年代的收集器,無法與 JDK 1.4.0 中就已經存在的新生代收集器 Parallel Scavenge 配合工作,是以在 JDK 1.5 中使用 CMS 來收集老年代的時候,新生代隻能選擇 ParNew 或者 Serial 收集器

在 G1 出現之前,CMS 使用還是非常廣泛的。一直到今天,仍然有很多系統使用 CMS GC

CMS 整個過程比之前的收集器要複雜,整個過程分為 4 個主要階段,即初始标記階段、并發标記階段、重新标記階段和并發清除階段

  • 初始标記(Initial - Mark)階段:

    在這個階段中,程式中所有的工作線程都将會因為 “Stop - The - World” 機制而出現短暫的暫停,這個階段的主要任務僅僅是标記出 GC Roots 能直接關聯到的對象。一旦标記完成之後就會恢複之前被暫停的所有應用線程。由于直接關聯對象比較小,是以這裡的速度非常快

  • 并發标記(Concurrent - Mark)階段:

    從 GC Roots 的直接關聯對象開始,周遊整個對象圖,這個過程耗時較長,但是不需要停頓使用者線程,可以與垃圾收集線程一起并發運作

  • 重新标記(Remark)階段:

    由于在并發标記階段中,程式的工作線程會和垃圾收集線程同時運作或交叉運作,是以為了修正并發标記期間,因使用者程式繼續運作而導緻标記産生變動的那一部分對象的标記記錄,這個階段的停頓時間通常會比初始标記階段稍長一些,但也遠比并發标記階段的時間短

  • 并發清除(Concurrent - Sweep)階段:

    此階段清理删除掉标記階段判斷已經死亡的對象,釋放記憶體空間。由于不需要移動存活對象,是以這個階段也是可以與使用者線程并發執行的

盡管 CMS 收集器采用的是并發回收(非獨占式),但是在其初始化标記和再次标記這兩個階段中仍然需要執行 “Stop - The - World” 機制暫停程式中的工作線程,不過暫停時間并不會太長,是以可以說明目前所有的垃圾收集器都做不到完全不需要 “Stop - The - World”,隻是盡可能地縮短暫停時間

由于最耗費時間的并發标記與并發清除階段都不需要暫停工作,是以整體的回收是低停頓的

另外,由于在垃圾收集階段,使用者線程沒有中斷,是以在 CMS 回收過程中,還應該確定應用程式使用者線程有足夠的記憶體可用。是以,CMS 收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,而是當堆記憶體使用率達到某一門檻值時,便開始進行回收,以確定應用程式在 CMS 工作過程中依然有足夠的空間支援應用程式運作。要是 CMS 運作期間預留的記憶體無法滿足程式需要,就會出現一次 “Concurrent Mode Failure” 失敗,這時虛拟機将會啟動後備預案,即臨時啟用 Serial Old 收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了

CMS 收集器的垃圾收集算法采用的是标記 - 清除算法,這意味着每次執行完記憶體回收後,由于被執行記憶體回收的對象所占用的記憶體空間極有可能是不連續的一些記憶體塊,不可避免地會産生一些記憶體碎片。那麼 CMS 在為新對象配置設定記憶體空間時,将無法使用指針碰撞(Bump The Poibter)技術,而隻能夠選擇空閑清單(Free List)執行記憶體配置設定

但是即使是這樣也不會采用标記 - 壓縮算法,因為當并發清除的時候,用 Compact 整理記憶體的話,原來的使用者線程使用的記憶體就無法使用了。要保證使用者線程能繼續執行的前提是它運作的資源不受影響。标記 - 壓縮适合 STW 的場景下使用

2.優點

  • 并發收集
  • 低延遲

3.缺點

  • 會産生記憶體碎片,導緻并發清除後,使用者線程可用的空間不足。在無法配置設定大對象的情況下,不得不提前觸發 Full GC
  • CMS 收集器會對 CPU 資源非常敏感。在并發階段,它雖然不會導緻使用者停頓,但是會因為占用了一部分線程而導緻應用程式變慢,總吞吐量會降低
  • CMS 收集器無法處理浮動垃圾。可能出現 “Concurrent Mode Failure” 失敗而導緻另一次 FUll GC 的産生。在并發标記階段,由于程式的工作線程和垃圾收集線程是同時運作或者交叉運作的,那麼在并發标記階段如果産生新的垃圾對象,CMS 将無法對這些垃圾對象進行标記,最終會導緻這些新産生的垃圾對象沒有被及時回收,進而隻能在下一次執行 GC 時釋放這些之前未被回收的記憶體空間

二、G1

1.介紹

誕生背景

及你拿來,應用程式所應對的業務越來越龐大、複雜,使用者越來越多,沒有 GC 就不能保證應用程式正常運作,而經常造成 STW 的 GC 又跟不上實際的需求,是以才會不斷地嘗試對 GC 進行優化.G1(Gabage First)垃圾收集器是 Java 7 update 4 之後引入的一個新的垃圾收集器,是當今收集器技術發展地最前沿成果之一

與此同時,為了适應現在不斷擴大的記憶體和不斷增加的處理器數量,進一步降低暫停時間(pause time),同時兼顧良好的吞吐量。官方給 G1 設定的目标是在延遲可控的情況下獲得盡可能高的吞吐量,是以才擔當起 “全功能收集器” 的重任與期望(同時負責新生代和老年代)

名字由來

G1 是一個并行回收器,它把堆記憶體分割為很多不相關的區域(Region)(實體上不連續)。使用不同的 Region 來表示 Eden、幸存者 0 區、幸存者 1 區、老年代等

G1 GC 有計劃地避免在整個 Java 堆中進行全區域的垃圾收集。G1 跟蹤各個 Region 裡面的垃圾堆積的價值大小(回收所獲得空間大小以及回收所需時間),在背景維護一個優先清單,每次根據允許的收集時間,優先回收價值最大的 Region

由于這種方式的側重點在于回收垃圾最大量的區域,是以我們給 G1 一個名字:垃圾優先(Garbage First)

2.優勢

并行與并發

  • 并行性:G1 在回收期間,可以有多個 GC 線程同時工作,有效利用多個 GC 線程同時工作,有效利用多和計算能力。此時使用者線程 STW
  • 并發性:G1 擁有與應用程式交替執行的能力,部分工作可以和應用程式同時執行。是以,一般來說,不會在整個回收階段發生完全阻塞應用程式的情況

分代收集

  • 從分代上看,G1 依然屬于分代型垃圾回收器,它會區分年輕代和老年代,年輕代依然有 Eden 區和 Survivor 區。但從堆的結構上看,它不要求整個 Eden 區、新生代或者老年代都是連續的,也不再堅持固定大小和固定數量
  • 将堆空間分為若幹個區域(Region),這些區域中包含了邏輯上的年輕代和老年代
  • 和之前的各類回收期不同,它同時兼顧新生代和老年代。對比其他回收器,或者工作在年輕代,或者工作在老年代

空間整合

  • CMS:“标記 - 清除” 算法、記憶體碎片、若幹次 GC 後進行一次碎片整理
  • G1:将記憶體劃分為一個個的 Region。記憶體的回收是以 Region作為基本機關的。Region 之間是複制算法,但整體上實際可看作是标記 - 壓縮(Mark - Compact)算法,兩種算法都可以避免記憶體碎片。這種特性有利于程式長時間運作,配置設定大對象時不會因為無法找到連續記憶體空間而提前出發下一次 GC。尤其是當 Java 堆非常大的時候,G1 的優勢更加明顯

可預測的停頓時間模型

即軟實時,這是 G1 相對于 CMS 的另一大優勢,G1 除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明确指定在一個長度為 M 毫秒的時間片段内,消耗在垃圾收集上的時間不得超過 N 毫秒

  • 由于分區的原因,G1 可以隻選取部分區域進行記憶體回收,這樣縮小了回收的範圍,是以對于全局停頓情況的發生也能得到較好的控制
  • G1 跟蹤各個 Region 裡面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在背景維護一個優先清單,每次根據允許的收集時間,優先回收價值最大的 Region。保證了 G1 收集器在有限的時間内可以擷取盡可能高的收集效率

相比于 CMS GC,G1 未必能做到 CMS 在最好情況下的延時停頓,但是最差情況要好很多

即便如此,G1 相較于 CMS 還不具備全方位、壓倒性優勢。比如在使用者程式運作過程中,G1 無論是為了垃圾收集産生的記憶體占用(Footprint),還是程式運作時的額外執行負載(Overload)都要比 CMS 高

從經驗上來說,在小記憶體應用上 CMS 的表現大機率會優于 G1,而 G1 在大記憶體應用上則更具優勢。平衡點在 6GB - 8GB 之間

簡化性能調優

G1 的設計原則就是簡化 JVM 性能調優,開發人員隻需要簡單的三步即可完成調優:

  1. 開啟 G1 垃圾收集器
  2. 設定堆的最大記憶體
  3. 設定最大的停頓時間

G1 中提供了三種垃圾回收模式:YoungGC、Mixed GC 和 Full GC,在不同的條件下被觸發

3.應用場景

  • 面向服務端應用,針對具有大記憶體、多處理器的機器(在普通大小的堆裡表現平平)
  • 主要為需要低 GC 延遲,并具有大堆的應用程式提供解決方案
  • 用來替換 JDK 1.5 中的 CMS 收集器,在下面的情況,使用 G1 可能比 CMS 好
    • 超過 50% 的 Java 堆被活動資料占用
    • 對用配置設定頻率或年代提升頻率變化很大
    • GC 停頓時間過長(大于 0.5s)
  • HotSpot 垃圾收集器裡,除了 G1 以外,其他的垃圾收集器都是使用内置的專門的 JVM 線程執行 GC 的多線程操作,而 G1 GC 可以采用應用程式線程承擔背景運作的 GC 工作,即當 JVM 的 GC 線程處理速度慢時,系統會調用應用程式線程幫助加速垃圾回收過程

4.Region 介紹

使用 G1 收集器時,它将整個 Java 堆劃分成約 2048 個大小相同的獨立 Region 塊,每個 Region 塊大小根據堆空間的實際大小而定,整體被控制在 1MB 到 32MB 之間,且為 2 的 N 次幂,即 1MB、2MB、4MB、8MB、16MB、32MB。可以通過參數設定。所有的 Region 大小相同,且在 JVM 生命周期内不會被改變

雖然還保留有新生代和老年代的概念,但新生代和老年代不再是實體隔離的了,它們都是一部分 Region(不需要連續)的集合。通過 Region 的動态配置設定方式實作邏輯上的連續

【重難點】【JVM 03】CMS、G1、ZGC【重難點】【JVM 03】CMS、G1、ZGC一、CMS二、G1三、ZGC
  • 一個 Region 有可能屬于 Eden、Survivor 或者 Old/Tenured 記憶體區域。但是一個 Region 同一時間隻可能屬于一個角色
  • G1 垃圾收集器還增加了一種新的記憶體區域,叫做 Humongous 記憶體區域,如果一個對象超過 1.5 個 Region,就會放到 Humongous。之是以這麼設計是因為,之前對于堆中的大對象,預設直接會被配置設定到老年代,但是如果它是一個短期存在的大對象,就會對垃圾收集器造成負面影響;而 G1 劃分了一個 Humongous 區,專門存放大對象。如果一個 H 區裝不下一個大對象,那麼 G1 就會尋找連續的 H 區來存儲。為了能找到連續的 H 區,有時候不得不啟動 Full GC。G1 的大多數行為都把 H 區作為老年代的一部分來看待

5.G1 垃圾回收過程

G1 GC 的垃圾回收過程主要包括如下三個環節:

  • 新生代 GC(Young GC)
  • 老年代并發标記過程(Concurrent Marking)
  • 混合回收
  • (如果需要,單線程、獨占式、高強度的 Full GC 還是繼續存在的。它針對 GC 的評估(參數預設)失敗提供了一種失敗保護機制,即強力回收)
【重難點】【JVM 03】CMS、G1、ZGC【重難點】【JVM 03】CMS、G1、ZGC一、CMS二、G1三、ZGC

應用程式配置設定記憶體,當新生代的 Eden 區用盡時開始新生代的回收。G1 的新生代收集階段是一個并行的獨占式收集器。在年輕代回收期,G1 GC 暫停所有應用程式線程,啟動多線程執行年輕代回收。然後從年輕代區間移動存活對象到 Survivor 區間或者 Old 區間,也有可能是兩種區間都會涉及

當堆記憶體使用達到一定值(預設 45%)時,開始老年代并發标記過程

标記完成馬上開始混合回收。在混合回收期,G1 GC 從 Old 區間移動存活對象到空閑區間,然後空閑區間就變成了 Old 區間。老年代的 G1 回收器和其他 GC 不同,G1 的老年代回收器不需要整個老年代都被回收,一次隻需要掃描/回收一小部分老年代的 Region 就可以了。同時,這個老年代的 Region 是和年輕代一起被回收的,而不是專門回收其中一種

舉個例子:一個 Web 伺服器,Java 程序最大堆記憶體為 4G,每分鐘響應 1500 個請求,沒 45 秒會新配置設定大約 2G 的記憶體。G1 會每 45 秒進行一次年輕代回收,每 31 個小時整個堆的使用率會達到 45%,會開始老年代并發标記過程,标記完成後開始四到五次的混合回收

1、年輕代 GC

JVM 啟動時,G1 先準備好 Eden 區,程式在運作過程中不斷建立對象到 Eden 區,當 Eden 空間耗盡時,G1 會啟動一次年輕代垃圾回收過程

年輕代 GC 時,首先 G1 停止應用程式的執行(Stop - The -World),G1 建立回收集(Collection Set),回收集是指需要被回收的記憶體分段的集合,年輕代回收過程的回收集合包含年輕代 Eden 區和 Survivor 區所有的記憶體分段

【重難點】【JVM 03】CMS、G1、ZGC【重難點】【JVM 03】CMS、G1、ZGC一、CMS二、G1三、ZGC

圖檔的上半部分表示回收前,下半部分表示回收後

詳細過程

  1. 掃描根

    根是指 static 變量指向的對象,正在執行的方法調用鍊條上的局部變量等。根引用連同記憶集(下面有介紹)記錄的外部引用作為掃描存活對象的入口

  2. 更新記憶集

    處理髒卡表(下面有介紹)中的 card,更新記憶表。此階段完成後,記憶集可以準确地反映老年代對所在的記憶體分段中對象的引用

  3. 處理記憶集

    識别被老年代對象指向的 Eden 中的對象,這些被指向的 Eden 中的對象被認為是存活的對象

  4. 複制對象

    此階段,對象樹被周遊,Eden 區記憶體段中存活的對象會被複制到 Survivor 區中空的記憶體分段,Suvivor 區記憶體段中存活的對象如果年齡未達到門檻值,年齡會加 1,達到門檻值會被複制到 Old 區中空的記憶體分段。如果 Survivor 空間不夠,Eden 空間的部分資料會直接晉升到老年代空間

  5. 處理引用

    處理 Sofr、Weak、Phanto、Final、JNI Weak 等引用。最終 Eden 空間的資料為空,GC 停止工作,而目标記憶體中的對象都是連續存儲的,沒有碎片,是以複制過程可以達到記憶體整理的效果,減少碎片

記憶集 Remembered Set

進行垃圾回收時,我們會遇到如下問題

  • 一個 Region 不可能是孤立的,一個 Region 中的對象可能被其他任意 Region 中的對象引用,判斷對象存活時,應該怎麼確定一個對象沒被其他任何對象引用?

解決方案

【重難點】【JVM 03】CMS、G1、ZGC【重難點】【JVM 03】CMS、G1、ZGC一、CMS二、G1三、ZGC
  • 無論 G1 還是其他分代收集器,JVM 都是使用 Remembered Set 來避免全局掃描
  • 每個 Region 都有一個對應的 Remembered Set
  • 每次 Reference 類型資料寫操作時都會産生一個 Write Barrier 暫時中斷操作
  • 然後檢查将要寫入的引用指向的對象是否和該 Reference 類型資料在不同的 Region(其他收集器是檢查老年代對象是否引用了新生代對象)
  • 如果不同,通過 CardTable 把相關引用資訊記錄到引用指向對象的所在 Region 對應的 Remembered Set 中
  • 當進行垃圾收集時,在 GC 根結點的枚舉範圍加入 Remembered Set。這樣,就可以保證不進行全局掃描,也不會有遺漏

髒卡表 Dirty Card Queue

對于應用程式的引用指派語句 object.field = object,JVM 會在之前和之後執行特殊的操作以在髒卡表中入隊一個儲存了對象引用資訊的 card。在年輕代回收的時候,G1 會對髒卡表中所有的 card 進行處理,以更新記憶集,保證 Rset 實時準确地反映引用關系

那為什麼不在引用指派語句處直接更新記憶集呢?

這是為了性能的需要,記憶集的處理需要線程同步,開小會很大,使用隊列性能會好很多

2、并發标記過程

  1. 初始标記階段

    标記從根結點直接可達的對象。這個階段是 STW 的,并且會觸發一次年輕代 GC

  2. 根區域掃描(Root Region Scanning)

    G1 GC 掃描 Survivor 區直接可達的老年代區域對象,并标記被引用的對象。這一過程必須在年輕代 GC 之前完成

  3. 并發标記(Concurrent Marking)

    在整個堆中進行并發标記(和應用程式并發執行),此過程可能被年輕代 GC 中斷。在并發标記階段,若發現區域對象中的所有對象都是垃圾,那這個區域會被立即回收。同時,并發标記過程中,會計算每個區域的對象活性(區域中存活對象的比例)

  4. 再次标記(Remark)

    由于應用程式持續進行,需要修正上一次的标記結果。是 STW 的。G1 中采用了比 CMS 更快的初始化快照算法 Snapshot -At - The - Begining(SATB)

  5. 獨占清理(cleanup,STW)

    計算各個區域的存活對象和 GC 回收比例,并進行排序,識别可以混合回收的區域,為下階段做鋪墊

  6. 并發清理階段

    識别并清理完全空閑的區域

3、混合回收

當越來越多的對象晉升到老年代區域時,為了避免堆記憶體被耗盡,虛拟機觸發一個混合的垃圾收集器,即 Mixed GC。除了回收整個新生代區域,還會回收一部分老年代區域。對于老年代區域可以選擇性地回收,進而對垃圾回收的耗時進行控制。此外,注意區分 Mixed GC 和 Full GC

【重難點】【JVM 03】CMS、G1、ZGC【重難點】【JVM 03】CMS、G1、ZGC一、CMS二、G1三、ZGC

并發标記結束以後,老年代中百分百為垃圾的記憶體分段已經被回收了,部分為垃圾的記憶體分段中的垃圾比例也被計算出來了。預設情況下,這些老年代的記憶體分段會分 8 次進行回收,次數可以通過參數設定

混合回收的算法和年輕代回收的算法完全一樣,隻是回收集(Collection Set)多了老年代的記憶體分段

由于老年代中的記憶體分段預設分 8 次回收,G1 會優先回收垃圾多的記憶體分段。垃圾占記憶體分段比例越高的,越會被先回收,可以通過參數設定門檻值決定,預設為 65%,意思是垃圾占記憶體分段比例要達到 65% 才回收。如果垃圾占比太低,意味着存活的對象占比高,在複制的時候會花費更多的時間

混合回收并不一定要進行 8 次。有一個參數:-XX:G1HeapWastePercent,預設值為 10%,意思是允許整個堆記憶體中有 10% 的空間被浪費,意味着如果發現可回收的垃圾占堆記憶體的比例低于 10%,則停止混合回收。因為付出的 GC 時間得不到比對的回報,即回收的垃圾太少

G1 Full GC

G1 的初衷是要避免 Full GC 的出現,但是如果上述方式不能正常工作,G1 會停止應用程式的執行(STW),使用單線程的記憶體回收算法進行垃圾回收,性能會非常差,應用程式停頓時間會很長

導緻 G1 Full GC 的原因有兩個:

  1. 回收的時候 Survivor 區沒有足夠的 to 空間來存放晉升的對象
  2. 并發處理過程完成之前空間耗盡

三、ZGC

1.介紹

《深入了解 Java 虛拟機》一書中這樣定義 ZGC:ZGC 收集器是一款基于 Region 記憶體布局的,(暫時)不設分代,使用了讀屏障、染色指針和多重映射等技術實作可并發的标記 - 壓縮算法、以低延遲為首要目标的一款垃圾收集器

ZGC 的工作過程可以分為 4 個階段:并發标記 - 并發預備重配置設定 - 并發重配置設定 - 并發重映射

ZGC 幾乎在所有地方并發執行,隻有初始标記需要 STW,并且這部分實際耗時也是非常少的

2.優勢

高吞吐量、低延遲

ZGC 支援 “NUMA-aware” 的記憶體配置設定。NUMA(Non-Uniform Memory Access,非同一記憶體通路的架構)是一種多處理器或多核處理器計算機所設計的記憶體架構

現在多 CPU 插槽的伺服器都是 NUMA 架構,比如兩顆 CPU 插槽(24 核),64 GB 記憶體的伺服器,每顆 CPU 都有一個從屬于自己的 32 GB 本地記憶體,CPU 通路本地記憶體肯定會比通路另一顆 CPU 的記憶體快,當然也會比不使用 NUMA 架構,而是兩顆 CPU 通路同一塊 64 GB 的共享記憶體要快

ZGC 預設支援 NUMA 架構,在建立對象時,根據目前線程在哪個 CPU 執行,優先在靠近這個 CPU 的記憶體進行配置設定,這樣可以顯著地提高性能,在 SPEC JBB 2005 基準測試裡獲得 40% 地提升

3.缺點

浮動垃圾

當 ZGC 準備要對一個很大地堆做一次完整地并發收集時,假設其全過程要持續十分鐘以上,由于應用的對象配置設定速率很高,将會建立大量的新對象,這就導緻這些新對象很難進入當次收集地标記範圍,通常就隻能全部作為存活對象來看待,盡管其中絕大部分對象都是朝生夕滅,是以産生了大量的浮動垃圾

目前唯一的辦法就是盡可能地區增加對容量大小,擷取更多喘息的時間。但若要從根本上解決,還是需要引入分代收集,讓新生對象都在一個專門的區域中建立,然後針對這個區域進行更頻繁、更快的收集

4.ZGC 的布局

與 Shenandoah、G1 一樣,ZGC 也采取基于 Region 的堆記憶體布局,但與他們不同的是,ZGC 的 Region 具有動态性(動态地建立和銷毀,以及動态的區域容量大小)

ZGC 的 Region 可以分為三類:

  • 小型 Region:容量固定為 2 MB,用于放置小于 256 KB 的小對象
  • 中型 Region:容量固定為 32 MB,用于防止大于等于 256 KB 但小于 4MB 的對象
  • 大型 Region:容量不固定,可以動态變化,但必須是 2 MB 的整數倍,用于存放 4 MB 以上的大對象,并且每個大型 Region 隻會存放一個對象

5.ZGC 的特性

Concurrent(并發執行)

ZGC 隻有短暫的 STW,大部分的過程都是和應用線程并發執行,比如最耗時的并發标記和并發移動過程

Region - based(記憶體基于 Region)

ZGC 中沒有新生代和老年代的概念,隻有一塊一塊的記憶體區域,但和 G1 不一樣的是,Region 的大小更加靈活和動态,不會像 G1 那樣在一開始就被劃分為固定大小

Compacting(壓縮)

每次進行 GC 時,都會對 Region 進行壓縮,是以完全避免了 CMS 算法中的碎片化問題

NUMA-aware(NUMA 架構)

ZGC 對 NUMA 的支援是小分區配置設定時會優先從本地記憶體配置設定,如果本地記憶體不足則從遠端記憶體配置設定。對于中、大分區的話就交由作業系統決定

上述做法的原因是生成的絕大部分都是小分區對象,是以優先本地配置設定速度較快,而且也不易造成記憶體不平衡的情況。而中、大分區對象較大,如果都從本地配置設定則可能會導緻記憶體不平衡

Using colored pointers(着色指針)

HotSpot 的垃圾收集器,有幾種不同的标記實作方案

  • 把标記直接記錄在對象頭上(Serial 收集器)
  • 把标記記錄在與對象互相獨立的資料結構上(G1、Shenandoah 使用了一種相當于堆記憶體的 1/64 大小的,稱為 BitMap 的結構來記錄标記資訊)
  • ZGC 染色指針直接把标記資訊記在引用對象的指針上

ZGC 染色指針的優勢:

  • 染色指針可以使得一旦某個 Region 的存活對象被移走之後,這個 Region 立刻就能被釋放和重用掉,而不必等待整個堆中所有指令向該 Region 的引用都被修正後才能清理
  • 染色指針可以大幅減少在垃圾收集過程中記憶體屏障的使用數量,設定記憶體屏障,尤其是寫屏障,目的通常是為了記錄對象引用的變動情況,如果将這些資訊直接維護在指針中,顯然可以省去一些專門的記錄操作
  • 染色指針可以作為一種可擴充的存儲結構用來記錄更多與對象标記、重定位過程相關的資料,以便日後進一步提高性能

Using load barriers(讀屏障)

在 CMS 和 G1 中都用到了寫屏障,而 ZGC 用到了讀屏障

寫屏障是在對象引用指派的時候的 AOP,而讀屏障是在讀取引用時的 AOP

因為在标記和移動過程中,GC 線程和應用線程是并發執行的,是以存在這種情況:對象 A 内部的引用所指的對象 B 在标記或者移動狀态,為了保證應用線程拿到的 B 對象是對的,那麼在讀取 B 的指針時會經過一個 “load barriers”,這個讀屏障可以保證在執行 GC 時,資料讀取的正确性

6.ZGC 的運作過程

ZGC 的運作過程大緻可劃分為以下四個大的階段。四個階段都是可以并發執行的,僅是兩個階段中間會存在短暫的停頓小階段。運作過程如下:

【重難點】【JVM 03】CMS、G1、ZGC【重難點】【JVM 03】CMS、G1、ZGC一、CMS二、G1三、ZGC
  • 并發标記(Concurrent Mark):

    與 G1、Shenandoah 一樣,并發标記是周遊對象圖做可達性分析的階段,前後也要經過類似于 G1、Shenandoah 的初始标記、最終标記的短暫停頓,而且這些停頓階段所做的事情在目标上也是相類似的

  • 并發預備重配置設定(Concurrent Prepare for Relocate):

    這個階段需要根據特定的查詢條件統計得出本次收集過程要清理哪些 Region,将這些 Region 組成重配置設定集(Relocation Set)

  • 并發重配置設定(Concurrent Relocate):

    重配置設定是 ZGC 執行過程中的核心階段,這個過程要把重配置設定集中的存活對象複制到新的 Region 上,并為重配置設定集中的每個 Region 維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關系

  • 并發重映射(Concurrent Remap):

    重映射所做的就是修正整個堆中指向重配置設定集中舊對象的所有引用,ZGC 并發映射并不是以一個必須要 “迫切” 去完成的任務。ZGC 很巧妙地把并發重映射階段要做的工作,合并到下一次垃圾收集循環中的并發标記階段裡去完成,反正它們都是要周遊所有對象的,這樣節省了一次周遊的開銷