1. 程式人生 > 其它 >JVM學習筆記-第三章-垃圾收集器與記憶體分配策略

JVM學習筆記-第三章-垃圾收集器與記憶體分配策略

JVM學習筆記-第三章-垃圾收集器與記憶體分配策略

tips:對於3.4之前的章節可見部落格:https://blog.csdn.net/sanhewuyang/article/details/95380620


3.5 經典垃圾收集器

3.5.1 Serial收集器

這個收集器是一個單執行緒工作的收集器,但它的單執行緒的意義並不僅僅是說明他只會使用一個處理器或一條收集執行緒去完成垃圾收集工作,更重要對的是強調在它進行垃圾收集時,必須暫停其他所有工作執行緒,直到它收集結束。

目前已經老無可用,但有著優於其他收集器的地方:簡單而高效

3.5.2 ParNew收集器

ParNew收集器實質上是Serial收集器的多執行緒並行版本。因為它是除了Serial收集器之外,目前唯一可以與CMS收集器配合工作的收集器,所以在JDK7之前的遺留系統中被作為首選的新生代收集器

CMS收集器是HotSpot虛擬機器中第一款真正意義上支援併發的垃圾收集器,首次實現了讓垃圾收集執行緒與使用者執行緒同時工作。但是當選用CMS作為老年代收集器時,新生代收集器只能選擇使用Serial收集器或者ParNew收集器

隨著垃圾收集器技術的不斷改進,G1收集器帶著CMS繼承者和代替者的光環登場。G1收集器是一個面向全堆的收集器,不需要其他新生代收集器的配合工作

3.5.3 Parallel Scavenge 收集器

Parallel Scavenge收集器也是一款新生代收集器,同樣是基於標記-複製演算法實現的收集器,也可以並行收集的多執行緒收集器。它的特點是它的關注點與其他收集器不同。CMS等收集器的關注點是儘可能地縮短垃圾收集時使用者執行緒的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量。
$$
吞吐量=\frac{執行使用者程式碼時間}{執行使用者程式碼時間+執行垃圾收集時間}
$$
提供了兩個引數用於精確控制吞吐量:

-XX:MaxGCPauseMillis 引數控制最大垃圾蒐集停頓時間,允許的值是一個大於0的毫秒數。收集器將盡力保證記憶體回收花費的時間不超過使用者的設定值。但是設定過分小的值並不能起到加快回收花費的速度的作用。

-XX:GCTimeRatio 引數直接設定吞吐量大小,允許的值是一個大於0小於100的整數。也就是垃圾收集時間佔總時間的比率。相當於吞吐量的倒數。

Parallel Scavenge 收集器還有一個引數:-XX:+UseAdaptiveSizePolicy 這是一個開關引數,當這個引數被啟用以後,就不需要人工指定新生代的大小,Eden與Survivor區的比例等等。虛擬機器會根據當前系統的執行情況收集效能監控資訊,動態調整這些引數。

3.5.4 Serial Old 收集器

Serial Old 是 Serial收集器的老年代版本,同樣是一個單執行緒收集器,使用標記-整理演算法。可能有兩種用途:1. 在JDK5以及之前的版本中與Parallel Scavenge收集器搭配使用 2. 作為CMS收集器發生失敗時的後備預案。

3.5.5 Parallel Old 收集器

Parallel Old 是 Parallel Scavenge收集器的老年代版本,支援多執行緒併發收集,基於標記-整理演算法實現,從JDK6版本開始提供。在注重吞吐量或者處理器資源較為稀缺的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器這個組合。

3.5.6 CMS 收集器

CMS收集器是一種以獲取最短回收停頓時間為目標的收集器,基於標記-清除演算法實現。整個運作過程分為4步:

步驟名稱 行為
初始標記(CMS initial mark) 標記一下GC Roots能直接關聯到的物件,需要Stop The World
併發標記(CMS concurrent mark) 從GC Roots的直接關聯物件開始遍歷整個物件圖的過程,可以與垃圾收集執行緒一起併發執行
重新標記(CMS remark) 修正併發標記期間,因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,需要Stop the World
併發清楚(CMS concurrent sweep) 清理刪除掉標記階段判斷的已經死亡的物件,可以與使用者執行緒同時併發完成

CMS收集器存在三個缺點:

1.CMS收集器對處理器資源非常敏感,預設啟動的回收執行緒數為(處理器核心數量+3)/ 4。在併發階段會因為佔用了一部分執行緒而導致應用程式變慢,降低總吞吐量。

為了緩解這種情況虛擬機器提供了“增量式併發收集器”(Incremental Concurrent Mark Sweep/i-CMS)作用是在併發標記、清理的時候讓收集器執行緒、使用者執行緒交替執行,儘量減少垃圾收集器執行緒的獨佔資源的時間,這樣整個垃圾收集的過程會更長,但是對使用者程式的影響就會顯得較少一些,直觀感受是速度變慢的時間更多了,但速度下降幅度就沒有那麼明顯。效果一般從jdk7開始被宣告為deprecated ,從JDK9釋出後被完全廢棄

2.由於CMS收集器無法處理“浮動垃圾”(Floating Garbage),有可能出現"Concurrent Mode Failure" 失敗進而導致另一完全"Stop The World"的Full GC的產生。

可以適當調高參數-XX:CMSInitiatingOccu-pancyFraction的值來提高CMS的觸發百分比,降低記憶體回收頻率,獲得更好的效能。如果設定的太高將會很容易導致大量的併發失敗產生,效能反而降低

3.由於基於標記-清除演算法,可能在收集結束時會有大量的空間碎片產生

通過調節:-XX:+UseCMSCompactAtFullCollection開關引數,預設是開啟的,從jdk9開始廢棄

*** -XX:CMSFullGCsBeforeCompaction 預設值是0,表示每次進入Full GC時都進行碎片整理***

3.5.7 Garbage First 收集器

Garbage First 收集器,簡稱 G1收集器,開創了收集器面向區域性收集的設計思路和基於Region的記憶體佈局形式。是一款主要面向服務端應用的垃圾收集器。可以面向堆記憶體的任何部分來組成回收集,衡量的標準不再是它屬於哪個分代,而是哪塊記憶體中存放的垃圾數量最多,回收收益最大。G1開創的基於Region的堆記憶體佈局是它能夠實現這個目標的關鍵,G1不再堅持固定大小以及固定數量的分代區域劃分,而是把連續的java堆劃分為多個大小相等的獨立區域(Region),每一個Region都可以根據需要,扮演新生代的Eden空間、Survivor空間或者老年代空間。

Region中還有一類特殊的Humongous區域,專門用來儲存大物件。G1認為只要大小超過了Region容量一半的物件就可以判定為大物件。每個Region的大小可以通過引數-XX:G1HeapRegionSize設定,取值範圍為1MB~32MB。

G1收集器之所以可以建立可預測的停頓時間模型,是因為它將Region作為單次回收的最小單元,即每次收集到的記憶體空間都是Region大小的整數倍,這樣可以有計劃地避免在整個JAVA堆中進行全區域的垃圾收集。更具體的思路是讓G1收集器區跟蹤各個Region裡面的垃圾堆積的價值大小,價值即回收所獲得的空間大小以及回收所需要的時間的經驗值,然後在後臺 維護一個優先順序列表,每次根據使用者設定允許的收集停頓時間(-XX:MaxGCPauseMillis)優先處理回收價值收益最大的那些Region。

G1的記憶集在儲存結構的本質上是一種雜湊表,Key是別的Region的起始地址,Value是一個集合,裡面儲存的元素是卡表的索引號。G1收集器通過原始快照(SATB)演算法實現了保證其不能打破原本的物件圖結構的目的。

G1收集器運作過程大致分為四個步驟:

步驟 行為
初始標記(Initial Marking) 標記一下GC Roots能直接關聯到的物件,並且修改TAMS指標的值。這個階段需要停頓執行緒,而且是借用進行Minor GC的時候同步完成的
併發標記(Concurrent Marking) 從GC Root開始對堆種物件進行可達性分析,遞迴掃描整個堆裡的物件圖,找出要回收的物件,可以與使用者程式併發執行。物件圖掃描完成以後,還需要重新處理SATB記錄下的在併發時有引用變動的物件
最終標記(Final Marking) 對使用者執行緒做另一個短暫的暫停,用於處理併發階段結束後仍遺留下來的最後那少量的SATB記錄
篩選回收(Live Data Counting and Evacuation) 負責更新Region的統計資料,對各個Region的回收價值和成本進行排序,根據使用者所期望的停頓時間來制定回收計劃,可以自由選擇任意多個Region構成回收集,然後把決定回收的那一部分Region的存活物件複製到空的Region中,再清理整個舊Region的全部空間,必須暫停使用者執行緒

G1收集器從整體來看是基於標記-整理演算法實現的,從區域性來看是基於標記-複製演算法來實現的。CMS的寫屏障實現是直接的同步操作,G1收集的寫屏障的實現是類似於訊息佇列的結構,將要做的操作存在佇列中,非同步進行處理。


3.6 低延遲垃圾收集器

3.6.1 Shenandoah收集器

Shenandoah收集器是一款只有OpenJDK才會包含的。與G1收集器相比,它們兩者有著相似的堆記憶體佈局,在初始標記、併發標記等許多階段的處理思路上都高度一致。但是在管理記憶體堆方面,與G1收集器至少有三個方面的明顯的不同之處:

  1. 支援併發的整理演算法:G1的回收階段是可以多執行緒並行的,但不能與使用者執行緒併發。Shenandoah後面會講到。
  2. Shenandoah收集器預設不使用分代收集。
  3. Shenandoah摒棄了在G1中耗費大量記憶體和計算資源去維護的記憶集,改名為“連線矩陣”(Connection Matrix)的全域性資料結構來記錄跨Region的引用關係。降低了處理跨代指標的記憶集維護消耗,也降低了偽共享問題發生的概率

Shenandoah收集器大致工作流程可以分為9個階段:

步驟名稱 動作
初始標記(Initial Marking) 標記與GC Roots直接關聯的物件,這個階段是Stop The World的,停頓時長與堆大小無關,與GC Roots的數量相關。
併發標記(Concurrent Marking) 遍歷物件圖,標記出全部可達的物件,這個階段與使用者執行緒一起併發的,時間長短取決於堆中存活物件的數量以及物件圖的結構複雜程度。
最終標記(Final Marking) 處理剩餘的SATB掃描,在這個階段統計出回收價值最高的Region,將這些Region構成一組回收集,最終標記階段也會有一小段短暫的停頓。
併發清理(Concurrent Cleanup) 清理那些整個區域內連一個存活物件都沒有找到的Region(這類Region被稱為Immediate Garbage Region)。
併發回收(Concurrent Evacuation) 核心差異!Shenandoah要把回收集裡面的存活物件先複製一份到其他未被使用的Region之中。Shenandoah會通過讀屏障和被成稱為"Brooks Points"的轉發指標來解決在複製物件時遇到的困難。時間長短取決於回收集的大小。
初始引用更新(Initial Update Reference) 把堆中所有指向舊物件的引用修正到複製後的新地址,這個操作稱為引用更新。在此階段,只是建立了一個執行緒集合點,確保所有的併發回收階段中進行的收集器執行緒都已完成分配給它們的物件移動任務而已。時間會很短,有一個十分短暫的停頓。
併發引用更新(Comcurrent Update Reference) 真正開始進行引用更新操作,與使用者執行緒一起併發的,時間長短取決於記憶體中涉及的引用數量的多少。只需要按照記憶體實體地址的順序,線性搜尋出引用型別,把舊值改為新值即可。
最終引用更新(Final Update Reference) 解決了堆中的引用更新後,還要修正存在於GC Roots中的引用。這個階段是最後一次停頓,時間與GC Roots的數量有關。
併發清理(Concurrent Cleanup) 此時整個回收集中所有的Region已再無存活物件,都變成了Immediate Garbage Regions了,最後呼叫一次併發清理過程來回收這些Region的記憶體空間,供以後新物件分配使用。

Brooks PointsBrooks是一個人的名字,它提出使用了轉發指標(Forwarding Pointer)的技術來實現物件移動與使用者程式併發的一種解決方案。不需要用到記憶體保護陷阱,而是在原有物件佈局結構的最前面統一增加一個新的引用欄位,在正常不處於併發移動的情況下,該引用指向物件自己。實際上Shenandoah收集器是通過比較並交換(Compare And Swap, CAS)操作來保證併發時物件的訪問正確性的。

JDK13中Shenandoah的記憶體屏障模型改進為基於引用訪問屏障(Load Reference Barrier)的實現,所謂“引用訪問屏障”是指記憶體屏障只攔截物件中資料型別為引用型別的讀寫操作,而不去管原生資料型別等其他非引用欄位的讀寫。這能省去大量對原生型別、物件比較、物件加鎖等場景中設定記憶體屏障所帶來的消耗。

3.6.2 ZGC收集器

ZGC收集器是一款基於Region記憶體佈局的,暫時不設分代的,使用了讀屏障、染色指標和記憶體多重對映等技術來實現可併發的標記-整理演算法的,以低延遲為首要目標的一款垃圾收集器。

ZGC的Region具有動態性-動態建立和銷燬,以及動態的區域容量大小。

染色指標(Colored Pointer):一種直接將少量額外的資訊儲存在指標上的技術。儘管在linux下64位指標的高18位不能用來定址,但是剩餘的46位所能支援的64TB記憶體仍然能夠充分滿足需要。鑑於此,將其高4位提取出來儲存四個標記資訊。通過這些標誌位,虛擬機器可以直接從指標中看到其引用物件的三色標記狀態、是否進入了重分配集、是否只能通過finalize( )方法才能被訪問到。也使得ZGC能夠管理的記憶體不可以超過4TB。使用染色指標的三大優勢:

  1. 可以使得一旦某個Region的存活物件被移走之後,這個Region立即就能被釋放和重用掉,不必等待整個堆中所有指向該Region的引用都被修正後才能清理。
  2. 可以大幅度減少在垃圾收集過程中的記憶體屏障的使用數量。到目前為止,ZGC都未使用寫屏障,只使用了讀屏障。
  3. 可以作為一種可擴充套件的儲存結構用來記錄更多與物件標記、重定位過程相關的資料,以便日後進一步提高效能。

Linux/x86-64平臺上的ZGC使用了多重對映將多個不同的虛擬記憶體地址對映到同一個實體記憶體地址上,意味著ZGC在虛擬記憶體中看到的地址空間要比實際的堆記憶體容量來得更大。把染色指標中的標誌位看作是地址的分段符,只要將這些不同的地址段都對映到同一個實體記憶體空間,經過多重對映轉換後,就可以使用染色指標正常進行定址了。

ZGC的運作過程(省略部分與之前介紹的G1和Shenandoah相同的小階段部分):

步驟 動作
併發標記(Concurrent Mark) 遍歷物件圖做可達性分析的階段,前後也要經過初始標記、最終標記的短暫停頓。ZGC的標記是在指標上而不是在物件上進行的,標記階段會更新染色指標中的Marked 0、Marked 1 標誌位。
併發預備重分配(Concurrent Prepare for Relocate) 根據特定的查詢條件統計得出本次收集過程要清理那些Region,將這些Region組成重分配集。與G1收集器的回收集還是有區別的,ZGC的重分配集只是決定了裡面的存活物件會被重新分配複製到其他的Region中,裡面的Region會被釋放,而並不能說回收行為就只是針對這個集合裡面的Region進行,因為標記過程是針對全堆的。
併發重分配(Concurrent Reolcate) 核心階段!把重分配集中的存活物件複製到新的Region上,並未重分配集中的每個Region維護一個轉發表,記錄從舊物件到新物件的轉向關係。
併發重對映(Concurrent Remap) 修正整個堆中指向重分配集中舊物件的所有引用。

END