深入理解Java虛擬機器 --- 垃圾回收器

ayu0v0發表於2024-11-08

Serial收集器

HotSpot虛擬機器執行在客戶端模式下的預設新生代收集器。

型別:單執行緒序列垃圾回收器

垃圾收集演算法:複製演算法

作用區域:新生代

特點:

1、只會用單個執行緒去完成垃圾收集工作,使用者執行緒會STW,直到收集結束。

2、沒有執行緒互動,專心做垃圾收集,獲得最高的單執行緒收集效率。

ParNew收集器

型別:多執行緒並行垃圾回收器

垃圾收集演算法:複製演算法

作用區域:新生代

本質上就是Serial收集器的並行版本。

特點:

1、能與CMS收集器搭配使用。

Parallel Scavenge收集器

型別:多執行緒並行垃圾回收器

垃圾收集演算法:複製演算法

作用區域:新生代

特點:

1、是以吞吐量優先的垃圾回收器。(吞吐量=使用者執行程式碼時間/使用者執行程式碼時間+垃圾收集時間)

2、不能與CMS收集器搭配使用。

3、內建有一個 PS MarkSweep收集器,但是實現原理跟Serial實現基本一樣。

為什麼CMS只能和ParNew搭配使用,而不能和Parallel Scavenge搭配使用?

就效能來看,Parallel Scavenge它的效能會比ParNew的效能要好一些。

但是CMS只能和ParNew搭配使用,原因如下:

1、CMS的設計目標是低延遲,Parallel Scavenge的設計目標是高吞吐量。

2、Parallel Scavenge沒有使用HotSpot的分代框架,而CMS使用了HotSpot的分代框架。(ParNew使用了HotSpot的分代框架)

Serial Old收集器

型別:單執行緒串型收集器

垃圾收集演算法:標記-整理演算法

作用區域:老年代

Parallel Old收集器

型別:多執行緒並行收集器

垃圾收集演算法:標記-整理演算法

作用區域:老年代

特點:

1、緩解了Parallel Scanvenge的尷尬局面。(在其出之前Parallel Scanvenge只能和Serial搭配使用)

CMS收集器

這款收集器是HotSpot虛擬機器中第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集執行緒與使用者執行緒同時工作。

CMS收集器的關注點是儘可能縮短垃圾收集時使用者執行緒的停頓時間。停頓時間越短(低延遲)就越適合與使用者互動的程式,良好的響應速度能提升使用者體驗。

型別:多執行緒併發收集器

垃圾收集演算法:標記-清除演算法

作用區域:老年代

工作原理

image.png

  • 初始標記階段:所有工作執行緒都會因為"Stop-the-World"機制而短暫暫停,這個階段主要任務僅僅只是標記出GC Roots能直接關聯到的物件

  • 併發標記階段:從GC Roots的直接關聯物件開始遍歷整個物件圖的過程,這個過程耗時較長但是不需要使用者執行緒停頓。掃描完成後,這個過程可能會造成物件引用的改變。CMS使用了增量標記的技術,把改變的都引用發起者(黑色物件)儲存起來。

  • 重新標記階段:主要是處理併發階段中儲存起來的黑色物件,在STW的前提下,重新以他們為根進行標記。

  • 併發清除階段:這個階段清除掉標記階段判斷已經死亡的物件,釋放記憶體空間。這個階段可以與使用者執行緒併發。

為什麼採用標記-清除演算法?

因為在清除過程中,GC執行緒是跟使用者併發的,如果使用複製或標記-整理演算法會造成物件記憶體地址的改變,那麼會造成使用者持有原先的引用而無法訪問到物件的情況。

特點

1、觸發CMS垃圾回收器時需要預留一定的空間來支援使用者建立物件。

觸發的闕值我們可以設定,但是在極端情況下,如果設定闕值過高,造成無法滿足程式分配物件,那麼虛擬機器就會被迫啟動Serial Old收集器來進行收集,這就會造成長時間的STW了。

2、會產生浮動垃圾

3、進行Full GC時,會進行記憶體碎片的整理

4、CMS對CPU資源非常敏感,在併發階段時,雖然不會造成使用者執行緒的STW,但是會因為佔有CPU資源,而導致應用程式變慢,降低總吞吐量。

G1垃圾收集器

具有跨時代意義的垃圾收集器,設計思想:區域性收集、基於Region的記憶體佈局形式

設計目的:回收垃圾最大化。

作用區域:整個堆

垃圾收集演算法:從整體上來看是標記-整理演算法。但從兩個Region的角度來看,是標記-複製演算法。

Region

image.png

G1保留了年輕代和老年代的概念,但新生代和老年代不再是固定的了,而是一系列不需要連續的動態集合。

其中,Homongous區域主要用來存放大物件。G1認為只要大小超過了一半的Region區域的物件就稱為大物件。大多數情況下,G1把Homongous當成老年代看待。

設定H區域的原因

之前遇到大物件且年輕代Eden空間不夠的話,那麼會將大物件放到老年代,那麼這個大物件得等到Old GC或者Full GC才能得以清理。

而設定H區之後:1、避免了記憶體碎片 2、提高了回收大物件的效率

工作原理

  • 初始標記階段:所有工作執行緒都會因為"Stop-the-World"機制而短暫暫停,這個階段主要任務僅僅只是標記出GC Roots能直接關聯到的物件

  • 併發標記階段:從GC Root開始對堆中物件進行可達性分析,這個過程與使用者執行緒併發執行。掃描完成後,這個過程中可能會存在物件引用的改變,G1使用原始快照(STAB)方式來避免出現漏標的情況。

  • 重新標記階段:主要處理併發標記過程中的STAB記錄,在STW的前提下,以他們為根進行重新標記。

  • 清除階段:這個階段需要使用者執行緒STW。這個階段清除掉標記階段判斷已經死亡的物件,釋放記憶體空間。(使用的是複製演算法)

問題

跨Region的引用問題:

當最開始我們使用分代模型的時候,只需要注意跨代引用,我們解決的思路是透過記憶集統計老年代到年輕代的引用即可。

而跨Region,我們就需要記錄得跟複雜了。解題思路還是透過記憶集來記錄跨Region引用,透過空間換時間的思路來避免全堆作為GC Root掃描。但是這個記憶集就需要每個Region都去維護自己獨特的一個了,每個Region的記憶集不僅要記錄我引用了誰,也要記錄誰引用了我。

所以,記憶集造成了比較大的空間浪費。(10%~20%的堆空間浪費)

併發標記階段如何避免漏標問題:

漏標:把存活的物件給他誤刪了。

G1使用了原始快照來解決漏標問題。當灰色物件要解開和白色物件的引用時,把白色物件記錄下來。並在後面以他們為根,去掃描,讓他們存活。(思路:就是以可能存在浮動垃圾的下,減少掃描的時間。)

Young GC & Mixed GC

G1透過構造一個可預測的時間模型,來在特定的時間內收穫最高的垃圾。

那麼在收集時,就不會只侷限於年輕代了,當涉及多個代時,我們稱為Mixed GC。

當只涉及年輕代時,我們還是稱為Young GC。

ZGC垃圾收集器

概述

ZGC是JDK11推出的低延遲垃圾回收器。

設計目標:

1、停頓時間不超過10ms(低停頓)

2、停頓時間不會隨著堆大小或者活躍物件的大小而增加。

適用場景:大記憶體低延遲服務

前者GC的痛點

前者的GC在STW這塊都不是特別友善,所以在一些低延遲服務造成了效能困擾。

CMS(ParNew)與G1停頓時間瓶頸

ParNew和G1使用的都是標記-複製演算法。

瓶頸定位→複製階段中的轉移階段中複製物件。

複製階段中轉移階段是STW的,轉移階段需要分配新記憶體和複製物件的成員變數。其中記憶體分配比較快,但是在複製一些複雜物件的時候耗時會比較長。

為什麼轉移階段不能和使用者併發執行呢?

主要是無法解決轉移過程中精確定位物件地址的問題。如果併發執行,那麼會導致使用者訪問不到具體物件。

ZGC的原理

全併發

ZGC採用標記-複製演算法。不過ZGC在標記、轉移和重定位階段幾乎都是併發的。

image.png

ZGC只有三個STW階段:初始標記,再標記,初始轉移。其中,初始標記和初始轉移分別都只需要掃描所有GC Roots,其處理時間和GC Roots的數量成正比,一般情況耗時非常短;再標記階段STW時間很短,最多1ms,超過1ms則再次進入併發標記階段。即,ZGC幾乎所有暫停都只依賴於GC Roots集合大小停頓時間不會隨著堆的大小或者活躍物件的大小而增加。與ZGC對比,G1的轉移階段完全STW的,且停頓時間隨存活物件的大小增加而增加

ZGC的技術

著色指標

image.png

著色指標是一種將資訊儲存在指標中的技術。

當應用程式建立物件時,首先在堆空間申請一個虛擬地址。同時會在M0、M1、Remapped地址空間分別申請一個虛擬地址,且三個虛擬地址對應同一個實體地址但這三個虛擬地址在同一時間只有一個空間有效

讀屏障

Object o = obj.FieldA   // 從堆中讀取引用,需要加入屏障
<Load barrier>
Object p = o  // 無需加入屏障,因為不是從堆中讀取引用
o.dosomething() // 無需加入屏障,因為不是從堆中讀取引用
int i =  obj.FieldB  //無需加入屏障,因為不是物件引用

ZGC中讀屏障的程式碼作用:在物件標記和轉移過程中,用於確定物件的引用是否滿足條件,並做出對應的動作。

ZGC的併發處理過程

1、初始化:ZGC初始化之後,整個記憶體空間的地址檢視被設定成Remapped。程式正常執行,在記憶體中分配物件,滿足一定條件後垃圾回收啟動,進入標記階段。

2、併發標記階段:第一次進入標記階段時檢視為M0,如果物件被GC標記執行緒或者應用執行緒訪問過,那麼就講物件的地址檢視從Remapped調整為M0。所以,在標記階段之後,如果在M0就說明物件是活躍的,如果是在Remapped就說明物件是不活躍的。

3、併發轉移階段:標記結束後就進入轉移階段,此時地址檢視再次被設定為Remapped,如果物件被GC標記執行緒或者應用程式訪問過,那麼會從M0調整為Remapped。

其實,在標記階段存在兩個地址檢視M0和M1,上面的過程顯示只用了一個地址檢視。之所以設計成兩個,是為了區別前一次標記和當前標記。也即,第二次進入併發標記階段後,地址檢視調整為M1,而非M0。

著色指標和讀屏障技術不僅應用在併發轉移階段,還應用在併發標記階段:將物件設定為已標記,傳統的垃圾回收器需要進行一次記憶體訪問,並將物件存活資訊放在物件頭中;而在ZGC中,只需要設定指標地址的第42~45位即可,並且因為是暫存器訪問,所以速度比訪問記憶體更快。

相關文章