0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

JVM的垃圾機制是如何工作的呢?

OSC開源社區(qū) ? 來源:OSCHINA 社區(qū) ? 2023-02-28 16:08 ? 次閱讀

前言

本文所有介紹僅限于HotSpot虛擬機,
本文先介紹了垃圾回收的必要手段,基于這些手段講解了歷代垃圾回收算法是如何工作的,
每一種算法不會講的特別詳細,只為讀者從算法角度理解工作原理,從而引出ZGC,方便讀者循序漸進地了解。
GC 是 Garbage Collection 的縮寫,顧名思義垃圾回收機制,即當需要分配的內(nèi)存空間不再使用的時候,JVM 將調(diào)用垃圾回收機制來回收內(nèi)存空間。

那么 JVM 的垃圾機制是如何工作的呢? 第一步識別出哪些空間不再使用(識別并標記出哪些對象已死); 第二步回收不再使用空間(清除已死對象 )

判斷對象是否已死

判斷對象是否已死通常有兩種方式,引用計數(shù)法和可達性分析法

引用計數(shù)法

給對象中添加一個引用計數(shù)器,每當有一個地方引用它時,計數(shù)器值就加 1: 當引用失效時,計數(shù)器值就減 1; 任何時刻計數(shù)器為 0 的對象就是不能再被使用的。 簡單高效,但無法解決循環(huán)引用問題,a=b,b=a 引用計數(shù)法并沒有在產(chǎn)品級的 JVM 中得到應(yīng)用

可達性分析法

這個算法的基本思路就是通過一系列的稱為 “GC Roots” 的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈 ( Reference Chain), 當一個對象到 GC Roots 沒有任何引用鏈相連 (用圖論的話來說,就是從 GC Roots 到這個對象不可達) 時,則證明此對象是不可用的。

?cde0f95a-aa86-11ed-bfe3-dac502259ad0.png

不過可達性算法中的對象并不是立即死亡的,對象擁有一次自我拯救的機會,對象被系統(tǒng)宣告死亡至少要經(jīng)歷兩次標記過程,第一次是經(jīng)過可達性分析之后沒有與 GC Roots 相連的引用鏈,第二次是在由虛擬機自動建立的Finalize隊列中判斷是否需要執(zhí)行 finalize () 方法。 HotSopt 虛擬機采用該算法。

清除已死對象的方式

標記清除算法

先標記再清除 不足:1 效率問題,標記和清除效率都不高。2 空間問題,產(chǎn)生大量空間碎片

復(fù)制算法

內(nèi)存分兩塊,A,B A 用完了,將存活對象拷貝到 B,A 清理掉 代價:內(nèi)存少了一半。 HotSopt 虛擬機用此算法回收新生代。將新生代內(nèi)存劃分為 8:1:1 的 Eden 和 Survivor 解決復(fù)制算法內(nèi)存使用率低的問題 ?

ce0a7ac8-aa86-11ed-bfe3-dac502259ad0.png??

標記整理算法

老年代使用,方式和標記清除類似,只是不直接清除,而是將后續(xù)對象向一端移動,并清理掉邊界以外的內(nèi)存。 ?

ce2094ca-aa86-11ed-bfe3-dac502259ad0.png

分代收集算法

分代收集是一個算法方案,整合了以上算法的優(yōu)點,一般是把 Java 堆分為新生代和老年代,在新生代中,使用復(fù)制算法老年代 “標記一清理” 或者 “標記一整理”

歷代垃圾收集器簡介

通過上文我們了解了怎樣識別垃圾,怎樣清理垃圾,接下來,講 ZGC 之前,我們回顧一下歷代垃圾回收是怎樣做的,主要是想給讀者一種歷史的視角,任何技術(shù)都不是憑空產(chǎn)生的,更多的是在前人成果之上進行優(yōu)化整合 我們先看一個歷代 JDK 垃圾收集器對比表格,以下表格著重說明或引出幾個問題: 1 CMS 從來未被當作默認 GC,且已廢棄 2 CMS 的思想其實部分被 ZGC 吸收,CMS 已死,但他的魂還在 3 JDK11、JDK17 為長期迭代版本,項目中應(yīng)優(yōu)先使用這兩個版本

版本 發(fā)布時間 默認收集器 事件
jdk1.3 2000-05-08 serial
jdk1.4 2004-02-06 ParNew
jdk1.5/5.0 2004-09-30 Parallel Scavenge/serial CMS 登場
jdk1.6/6.0 2006-12-11 Parallel Scavenge/Parallel Old
dk1.7/7.0 2011-07-28 Parallel Scavenge/Parallel Old G1 登場
jdk1.8/8.0 2014-03-18 Parallel Scavenge/Parallel Old
jdk1.9/9.0 2014-09-8 G1 CMS 廢棄
jdk10 2018-03-21 G1
jdk11 2018-09-25 G1 ZGC 登場
jdk12 2019-3 G1 Shenandoah
jdk13 2019-9 G1
jdk14 2020-3 G1 CMS 移除
jdk15 2020-9-15 G1 ZGC、Shenandoah 轉(zhuǎn)正
jdk16 2021-3-16 G1
jdk17 2021-09-14 G1 ZGC 分代
jdk18 2022-3-22 G1
jdk19 2022-9-22 G1

GC 分類

我們經(jīng)常在各種場景聽到以下幾種 GC 名詞,Young GC、Old GC、Mixed GC、Full GC、Major GC、Minor GC,他們到底什么意思,本人進行了以下梳理 首先 GC 分兩類,Partial GC(部分回收),F(xiàn)ull GC Partial GC:并不收集整個 GC 堆的模式,以下全是 Partial GC 的子集 Young GC:只收集 young gen 的 GC Old GC:只收集 old gen 的 GC。

只有 CMS 的 concurrent collection 是這個模式 Mixed GC:只有 G1 有這個模式,收集整個 young gen 以及部分 old gen 的 GC。

Minor GC:只有 G1 有這個模式,收集整個 young gen Full GC:收集整個堆,包括 young gen、old gen、perm gen(如果存在的話)等所有部分的模式。

Major GC:通常是跟 full GC 是等價的

serial 收集器

單線程收集器,“單線程” 的意義并不僅僅說明它只會使用一個 CPU 或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程, 直到它收集結(jié)束。它依然是虛擬機運行在 Client 模式下的默認新生代收集器。

它也有著優(yōu)于其他收集器的地方:簡單而高效 (與其他收集器的單線程比), 對于限定單個 CPU 的環(huán)境來說,Serial I 收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。 下圖彩色部分說明了它的算法,簡單粗暴 1 停止用戶線程 2 單線程垃圾回收新生代 3 重啟用戶線程 ??

ce3dc5d6-aa86-11ed-bfe3-dac502259ad0.png

ParNew 收集器

Parnew 收集器其實就是 Serial l 收集器的多線程版本。它是許多運行在 Server 模式下的虛擬機中首選的新生代收集器,其中有一個與性能無關(guān)但很重要的原因是,除了 Serial 收集器外,目前只有它能與 CMS 收集器配合工作。Pardew 收集器在單 CPU 的環(huán)境中絕對不會有比 Serial 收集器更好的效果。

它默認開啟的收集線程數(shù)與 CPU 的數(shù)量相同,在 CPU 非常多 (臂如 32 個) 的環(huán)境下,可以使用 - XX: ParallelGCThreads 參數(shù)來限制垃圾收集的線程數(shù)。

ParNew 收集器追求降低 GC 時用戶線程的停頓時間,適合交互式應(yīng)用,良好的反應(yīng)速度提升用戶體驗. 下圖彩色部分說明了它的算法,同樣簡單粗暴 1 停止用戶線程 2 多線程垃圾回收新生代 3 重啟用戶線程

ce5f70b4-aa86-11ed-bfe3-dac502259ad0.png

Parallel Scavenge 收集器

Parallel Scavenge 收集器是一個新生代收集器,它也是使用復(fù)制算法的收集器,又是并行的多線程收集器。算法的角度它和 ParNew 一樣,在此就不畫圖解釋了 Parallel Scavenge 收集器的目標則是達到一個可控制的吞吐量( Throughput) 吞吐量是指用戶線程運行時間占 CPU 總時間的比例 通過以下兩種方式可達到目的: 1. 在多 CPU 環(huán)境中使用多條 GC 線程,從而垃圾回收的時間減少,從而用戶線程停頓的時間也減少; 2. 實現(xiàn) GC 線程與用戶線程并發(fā)執(zhí)行。

Serial Old 收集器

Serial Old 是 Serial 收集器的老年代版本,它同樣是一個單線程收集器,使用 “標記整理” 算法。這個收集器的主要意義也是在于給 Client 模式下的虛擬機使用。 如果在 Server 模式下,那么它主要還有兩大用途: 一種用途是在 JDK1.5 以及之前的版本中與 ParallelScavenge 收集器搭配使用, 另一種用途就是作為 CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生 Concurrent Mode Failure 時使用 下圖彩色部分說明了它的算法,同樣簡單粗暴 1 停止用戶線程 2 單線程垃圾回收老年代 3 重啟用戶線程

?? ce7bcfc0-aa86-11ed-bfe3-dac502259ad0.png

Parallel Old 收集器

Paralle Old 是 Parallel Scavenge 收集器的老年代版本,一般它們搭配使用,追求 CPU 吞吐量,使用多線程和 “標記一整理” 算法。 下圖彩色部分說明了它的算法,同樣簡單粗暴 1 停止用戶線程 2 多線程垃圾回收老年代 3 重啟用戶線程

cea17ea0-aa86-11ed-bfe3-dac502259ad0.png

CMS 收集器

以上 5 種垃圾回收原理不難理解,算法之所以如此簡單個人理解在當時使用這種算法就夠了,隨著 JAVA 的攻城略地,有一種垃圾回收需求出現(xiàn),即使用盡量短的回收停頓時間,以避免過久的影響用戶線程,CMS 登場了。 CMS (Concurrent Mark Sweep) 收集器是一種以獲取最短回收停頓時間為目標的收集器。

想要達到目的,就要分析 GC 時最占用時間的是什么操作,比較浪費時間的是標記已死對象、清除對象,那么如果可以和用戶線程并發(fā)的進行,GC 的停頓基本就限制在了標記所花費的時間。

?ceb78966-aa86-11ed-bfe3-dac502259ad0.png

如上圖,CMS 收集器是基于 “標記一清除” 法實現(xiàn)的,它的運作過程分為 4 個步驟

?初始標記 (EMS initial mark) stop the world

?并發(fā)標記 (CMS concurrent mark)

?重新標記 (CMS remark) stop the world

?并發(fā)清除 (CMS concurrent sweep)

初始標記的作用是查找 GC Roots 集合的過程,這個過程處理對象相對較少,速度很快。(為什么要進行初始標記:枚舉根結(jié)點。并發(fā)標記是實際標記所有對象是否已死的過程,比較耗時,所以采用并發(fā)的方式。

重新標記主要是處理并發(fā)標記期間所產(chǎn)生的新的垃圾。

重新標記階段不需要再重新標記所有對象,只對并發(fā)標記階段改動過的對象做標記即可。 優(yōu)點: 并發(fā)收集、低停頓 缺點: CMS 收集器對 CPU 資源非常敏感。

CMS 收集器無法處理浮動垃圾 (Floating Garbage), 可能出現(xiàn) “Concurrent ModeFailure” 失敗而導(dǎo)致另一次 Full GC 的產(chǎn)生。

“標記一清除” 法導(dǎo)致大量空間碎片產(chǎn)生,以至于老年代還有大量空間,卻沒有整塊空間存儲某對象。

Concurrent ModeFailure可能原因及方案
原因1:CMS觸發(fā)太晚
方案:將-XX:CMSInitiatingOccupancyFraction=N調(diào)小 (達到百分比進行垃圾回收);
原因2:空間碎片太多
方案:開啟空間碎片整理,并將空間碎片整理周期設(shè)置在合理范圍;
-XX:+UseCMSCompactAtFullCollection (空間碎片整理)
-XX:CMSFullGCsBeforeCompaction=n
原因3:垃圾產(chǎn)生速度超過清理速度
晉升閾值過小;
Survivor空間過小,導(dǎo)致溢出;
Eden區(qū)過小,導(dǎo)致晉升速率提高;存在大對象;

G1 收集器

G1 是一款面向服務(wù)端應(yīng)用的垃圾收集器。下文會簡單講解一下它的 “特點” 和 “內(nèi)存分配與回收策略”,有基礎(chǔ)或不感興趣的同學(xué)直接跳到 “G1 垃圾回收流程”

特點

并行與并發(fā) G1 能充分利用多 CPU、多核環(huán)境下的硬件優(yōu)勢,使用多個 CPU (CPU 或者 CPU 核心) 來縮短 Stop-The- World 停頓的時間,部分其他收集器原本需要停頓 Java 線程執(zhí)行的 GC 動作,G1 收集器仍然可以通過并發(fā)的方式讓 Java 程序繼續(xù)執(zhí)行。

分代收集 與其他收集器一樣,分代概念在 G1 中依然得以保留。雖然 G1 可以不需要其他收集器配合就能獨立管理整個 GC 堆,但它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時間、熬過多次 GC 的舊對象以獲取更好的收集效果。

空間整合 與 CMS 的 “標記一清理” 算法不同,G1 從整體來看是基于 “標記一整理” 算法實現(xiàn)的收集器,從局部 (兩個 Region 之間) 上來看是基于 “復(fù)制” 算法實現(xiàn)的,但無論如何,這兩種算法都意味著 G1 運作期間不會產(chǎn)生內(nèi)存空間碎片,收集后能提供規(guī)整的可用內(nèi)存。這種特性有利于程序長時間運行,分配大對象時不會因為無法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次 GC。

可預(yù)測的停頓 這是 G1 相對于 CMS 的另一大優(yōu)勢,降低停頓時間是 G1 和 CMS 共同的關(guān)注點,但 G1 除了追求低停頓外,還能建立可預(yù)測的停頓時間模型,能讓使用者明確指定在一個長度為 M 毫秒的時間片段內(nèi),消耗在垃圾收集上的時間不得超過 N 毫秒,這幾乎已經(jīng)是實時 Java (RTSJ) 的垃圾收集器的特征了。

在 G1 之前的其他收集器進行收集的范圍都是整個新生代或者老年代,而 G1 不再是這樣。使用 G1 收集器時,Java 堆的內(nèi)存布局就與其他收集器有很大差別,它將整個 Java 堆劃分為多個大小相等的獨立區(qū)域 (Region), 雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分 Region (不需要連續(xù)) 的集合

內(nèi)存分配與回收策略

對象優(yōu)先在 Eden 分配 大多數(shù)情況下,對象在新生代 Eden 區(qū)中分配。當 Eden 區(qū)沒有足夠空間進行分配時,虛擬機將發(fā)起一次 Minor[?ma?n?(r)] GC 大對象直接進入老年代 所謂的大對象是指,需要大量連續(xù)內(nèi)存空間的 Java 對象,最典型的大對象就是那種很長的字符串以及數(shù)組。

大對象對虛擬機的內(nèi)存分配來說就是一個壞消息 (比遇到一個大對象更加壞的消息就是遇到一群 “朝生夕滅” 的 “短命大對象” 寫程序的時候應(yīng)當避免), 經(jīng)常出現(xiàn)大對象容易導(dǎo)致內(nèi)存還有不少空間時就提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間來 “安置” 它們。

長期存活的對象將進入老年代 虛擬機給每個對象定義了一個對象年齡 (Age) 計數(shù)器。如果對象在 Eden 出生并經(jīng)過第一次 Minor GC 后仍然存活,并且能被 Survivor 容納的話,將被移動到 Survivor 空間中,并且對象年齡設(shè)為 1。對象在 Survivor 區(qū)中每 “熬過” 一次 Minor GC, 年齡就增加 1 歲,當它的年齡增加到一定程度(默認 15 歲)會被晉升到老年代中。

對象晉升老年代的年齡閾值,可以通過參數(shù)據(jù) - XX : MaxTenuringThreshold 設(shè)置 動態(tài)對象年齡判定 為了能更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機并不是水遠地要求對象的年齡必須達到了 MaxTenuringThreshold 才能晉升老年代,如果在 Survivor 空間中相同年齡所有對象大小的總和大于 Survivor 空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到 MaxTenuringThreshold 中要求的年齡。

空間分配擔(dān)保 在發(fā)生 Minor GC 之前,虛擬機會先檢査老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果這個條件成立,那么 Minor GC 可以確保是安全的。如果不成立,則虛擬機會查看 HandlePromotionFailure 設(shè)置值是否允許擔(dān)保失敗。

如果允許,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試著進行一次 Minor GC, 盡管這次 Minor GC 是有風(fēng)險的;如果小于,或者 HandlePromotionFailure 設(shè)置不允許冒險,那這時也要改為進行一次 Full GC. 為什么要擔(dān)保: Minor GC 后還有大量對象存活且空間不夠存放新對象,就要直接在老年代存放 為什么是歷次晉升到老年代對象的平均大小: 取平均值進行比較其實仍然是一種動態(tài)概率的手段,也就是說,如果某次 Minor GCd 存活后的對象突增,遠遠高于平均值的話,依然會導(dǎo)致?lián)J?(HandlePromotionFailure) 如果出現(xiàn)了 HandlePromotionFailure 失敗,那就只好在失敗后重新發(fā)起一次 Full GC。

雖然擔(dān)保失敗時繞的子是最大的,但大部分情況下都還是會將 HandlePromotionFailure 開關(guān)打開,避免 Full GC 過于頻繁。 eden 的大小范圍默認是 =【-XX:G1NewSizePercent,-XX:G1MaxNewSizePercent】=【整堆 5%,整堆 60%】 humongous 如果一個對象的大小已經(jīng)超過 Region 大小的 50% 了,那么就會被放入大對象專門的 Region 中,這種 Region 我們叫 humongous

G1 垃圾回收流程

?cec93e0e-aa86-11ed-bfe3-dac502259ad0.png?

網(wǎng)上對 G1 的回收階段有不同的說法,參考 Oracle JVM 工程師的一個說法: 他把整個 G1 的垃圾回收階段分成了這么三個,第一個叫 Minor GC,就是對新生代的垃圾收集,第二個階段呢叫 Minor GC + Concurrent Mark,就是新生代的垃圾收集同時呢會執(zhí)行一些并發(fā)的標記,這是第二個階段,第三個階段呢它叫 Mixed GC 混合收集,這三個階段是一個循環(huán)的過程。

剛開始是這個新生代的垃圾收集,經(jīng)過一段時間,當老年代的內(nèi)存超過一個閾值了,它會在新生代垃圾收集的同時進行并發(fā)的標記,等這個階段完成了以后,它會進行一個混合收集,混合收集就是會對新生代、幸存區(qū)還有老年代都來進行一個規(guī)模較大的一次收集,等內(nèi)存釋放掉了,混合收集結(jié)束。這時候伊甸園的內(nèi)存都被釋放掉,它會再次進入新生代的一個垃圾收集過程,那我們先來看看這個新生代的收集 Minor GC。

Minor GC 的回收過程(eden 滿了回收)

選定所有 Eden Region 放入 CSet,使用多線程復(fù)制算法將 CSet 的存活對象復(fù)制到 Survivor Region 或者晉升到 Old Region。 下圖分 7 步演示了這個過程 1 初始狀態(tài),堆無占用 2 Eden Region 滿了進行標記 3 將存活對象復(fù)制到 Survivor Region 4 清理 Eden Region 5 Eden Region 又滿了進行再次標記,此時會連帶 Survivor Region 一起標記 6 將存活對象復(fù)制到另一個 Survivor Region 7 再次清理 Eden Region 和被標記過的 Survivor Region ?

cee18112-aa86-11ed-bfe3-dac502259ad0.png Minor GC 結(jié)束后自動進行并發(fā)標記,為以后可能的 Mixed GC 做準備

Mixed GC 的回收過程(專注垃圾最多的分區(qū))

選定所有 Eden Region 和全局并發(fā)標記計算得到的收益較高的部分 Old Region 放入 CSet,使用多線程復(fù)制算法將 CSet 的存活對象復(fù)制到 Survivor Region 或者晉升到 Old Region。 當堆空間的占用率達到一定閾值后會觸發(fā) Mixed GC(默認 45%,由參數(shù)決定) Mixed GC 它一定會回收年輕代,并會采集部分老年代的 Region 進行回收的,所以它是一個 “混合” GC。 下圖分 3 步演示了這個過程 1 并發(fā)標記所有 Region 2 并發(fā)復(fù)制 3 并發(fā)清理 ?

cefbfede-aa86-11ed-bfe3-dac502259ad0.png?

ZGC


ZGC(Z Garbage Collector) 是一款性能比 G1 更加優(yōu)秀的垃圾收集器。ZGC 第一次出現(xiàn)是在 JDK 11 中以實驗性的特性引入,這也是 JDK 11 中最大的亮點。在 JDK 15 中 ZGC 不再是實驗功能,可以正式投入生產(chǎn)使用了。

目標低延遲

?保證最大停頓時間在幾毫秒之內(nèi),不管你堆多大或者存活的對象有多少。

?可以處理 8MB-16TB 的堆

通過以上歷代垃圾回收器的講解,我們大致了解到減少延遲的底層思想不外乎將 stop the world 進行極限壓縮,將能并行的部分全部采用和用戶線程并行的方式處理,然而 ZGC 更 "過分" 它甚至把一分部垃圾回收的工作交給了用戶線程去做,那么它是怎么做到的呢?ZGC 的標記和清理工作同 CMS、G1 大致差不多,仔細看下圖的過程,和 CMS 特別像,這就是我在上文說的 CMS 其實并沒有真正被拋棄,它的部分思想在 ZGC 有發(fā)揚。

??cf19482c-aa86-11ed-bfe3-dac502259ad0.png

ZGC 的步驟大致可分為三大階段分別是標記、轉(zhuǎn)移、重定位。 標記:從根開始標記所有存活對象 轉(zhuǎn)移:選擇部分活躍對象轉(zhuǎn)移到新的內(nèi)存空間上 重定位:因為對象地址變了,所以之前指向老對象的指針都要換到新對象地址上。 并且這三個階段都是并發(fā)的。

初始轉(zhuǎn)移需要掃描 GC Roots 直接引用的對象并進行轉(zhuǎn)移,這個過程需要 STW,STW 時間跟 GC Roots 成正比。 并發(fā)轉(zhuǎn)移準備 :分析最有回收價值 GC 分頁(無 STW) 初始轉(zhuǎn)移應(yīng)對初始標記的數(shù)據(jù) 并發(fā)轉(zhuǎn)移應(yīng)對并發(fā)標記的數(shù)據(jù) 除了標記清理過程繼承了 CMS 和 G1 的思想,ZGC 要做了以下優(yōu)化

并發(fā)清理(轉(zhuǎn)移對象)

在 CMS 和 G1 中都用到了寫屏障,而 ZGC 用到了讀屏障。 寫屏障是在對象引用賦值時候的 AOP,而讀屏障是在讀取引用時的 AOP。 比如Object a = obj.foo;,這個過程就會觸發(fā)讀屏障。

也正是用了讀屏障,ZGC 可以并發(fā)轉(zhuǎn)移對象,而 G1 用的是寫屏障,所以轉(zhuǎn)移對象時候只能 STW。 簡單的說就是 GC 線程轉(zhuǎn)移對象之后,應(yīng)用線程讀取對象時,可以利用讀屏障通過指針上的標志來判斷對象是否被轉(zhuǎn)移。

讀屏障會對應(yīng)用程序的性能有一定影響,據(jù)測試,對性能的最高影響達到 4%,但提高了 GC 并發(fā)能力,降低了 STW。這就是上面所說的 ZGC “過分” 地將部分垃圾回收工作交給用戶線程的原因。

染色指針

染色指針其實就是從 64 位的指針中,拿幾位來標識對象此時的情況,分別表示 Marked0、Marked1、Remapped、Finalizable。 ?

cf363c8e-aa86-11ed-bfe3-dac502259ad0.png

0-41 這 42 位就是正常的地址,所以說 ZGC 最大支持 4TB (理論上可以 16TB) 的內(nèi)存,因為就 42 位用來表示地址 也因此 ZGC 不支持 32 位指針,也不支持指針壓縮。

其實對象只需要兩個狀態(tài) Marked,Remapped,對象被標記了,對象被重新映射了,為什么會有 M0,M1,用來區(qū)分本次 GC 標記和上次 GC 標記 以下是標記轉(zhuǎn)移算法說明: 1 在垃圾回收開始前:Remapped 2 標記過程: 標記線程訪問 發(fā)現(xiàn)對象地址視圖是 Remapped 這時候?qū)⒅羔槝擞洖?M0 發(fā)現(xiàn)對象地址視圖是 M0,則說明這個對象是標記開始之后新分配的或者已經(jīng)標記過的對象,所以無需處理 應(yīng)用線程 如果創(chuàng)建新對象,則將其地址視圖置為 M0 3 標記階段結(jié)束后 ZGC 會使用一個對象活躍表來存儲這些對象地址,此時活躍的對象地址視圖是 M0 4 并發(fā)轉(zhuǎn)移階段 轉(zhuǎn)移線程: 轉(zhuǎn)移成功后對象地址視圖被置為 Remapped(也就是說 GC 線程如果訪問到對象,此時對象地址視圖是 M0,并且存在或活躍表中,則將其轉(zhuǎn)移,并將地址視圖置為 Remapped ) 如果在活躍表中,但是地址視圖已經(jīng)是 Remapped 說明已經(jīng)被轉(zhuǎn)移了,不做處理。

應(yīng)用線程: 如果創(chuàng)建新對象,地址視圖會設(shè)為 Remapped 5 下次標記使用 M1 M1 標識本次垃圾回收中活躍的對象 M0 是上一次回收被標記的對象,但是沒有被轉(zhuǎn)移,且在本次回收中也沒有被標記活躍的對象。

下圖展示了 Marked,Remapped 的過程, 初始化時 A,B,C 三個對象處于 Remapped 狀態(tài) 第一次 GC,A 被轉(zhuǎn)移,B 未被轉(zhuǎn)移,C 無引用將被回收 第二次 GC,由于 A 被轉(zhuǎn)移過了(Remapped 狀態(tài)),所以被標記 M1,此時恰好 B 為不活躍對象,將被清理 第三次 GC,A 又被標記成 M0 ??

cf5b2828-aa86-11ed-bfe3-dac502259ad0.png

多重映射

Marked0、Marked1 和 Remapped 三個視圖 ZGC 為了能高效、靈活地管理內(nèi)存,實現(xiàn)了兩級內(nèi)存管理:虛擬內(nèi)存和物理內(nèi)存,并且實現(xiàn)了物理內(nèi)存和虛擬內(nèi)存的映射關(guān)系 在 ZGC 中這三個空間在同一時間點有且僅有一個空間有效,利用虛擬空間換時間,這三個空間的切換是由垃圾回收的不同階段觸發(fā)的,通過限定三個空間在同一時間點有且僅有一個空間有效高效的完成 GC 過程的并發(fā)操作 ?

cf78281a-aa86-11ed-bfe3-dac502259ad0.png

支持 NUMA

NUMA 是非一致內(nèi)存訪問的縮寫 (Non-Uniform Memory Access,NUMA) 早年如下圖:SMP 架構(gòu) (Symmetric Multi-Processor),因為任一個 CPU 對內(nèi)存的訪問速度是一致的,不用考慮不同內(nèi)存地址之間的差異,所以也稱一致內(nèi)存訪問(Uniform Memory Access, UMA )。這個核心越加越多,漸漸的總線和北橋就成為瓶頸,那不能夠啊,于是就想了個辦法。 ?

cf8f72b8-aa86-11ed-bfe3-dac502259ad0.png ??

把 CPU 和內(nèi)存集成到一個單元上,這個就是非一致內(nèi)存訪問 (Non-Uniform Memory Access,NUMA)。

cfa97686-aa86-11ed-bfe3-dac502259ad0.png

?ZGC 對 NUMA 的支持是小分區(qū)分配時會優(yōu)先從本地內(nèi)存分配,如果本地內(nèi)存不足則從遠程內(nèi)存分配。

ZGC 優(yōu)劣

綜上分析,ZGC 在戰(zhàn)略上沿用了上幾代 GC 的算法策略,采用并發(fā)標記,并發(fā)清理的思路,在戰(zhàn)術(shù)上,通過染色指針、多重映射,讀屏障等優(yōu)化達到更理想的并發(fā)清理,通過支持 NUMA 達到了更快的內(nèi)存操作。但 ZGC 同樣不是銀彈,它也有自身的優(yōu)缺點,如下

優(yōu)勢:

1、一旦某個 Region 的存活對象被移走之后,這個 Region 立即就能夠被釋放和重用掉,而不必等待整個堆中所有指向該 Region 的引用都被修正后才能清理,這使得理論上只要還有一個空閑 Region,ZGC 就能完成收集。

2、顏色指針可以大幅減少在垃圾收集過程中內(nèi)存屏障的使用數(shù)量,ZGC 只使用了讀屏障。

3、顏色指針具備強大的擴展性,它可以作為一種可擴展的存儲結(jié)構(gòu)用來記錄更多與對象標記、重定位過程相關(guān)的數(shù)據(jù),以便日后進一步提高性能。

劣勢:

1、它能承受的對象分配速率不會太高 ZGC 準備要對一個很大的堆做一次完整的并發(fā)收集。在這段時間里面,由于應(yīng)用的對象分配速率很高,將創(chuàng)造大量的新對象,這些新對象很難進入當次收集的標記范圍,通常就只能全部當作存活對象來看待 —— 盡管其中絕大部分對象都是朝生夕滅的,這就產(chǎn)生了大量的浮動垃圾。

如果這種高速分配持續(xù)維持的話,每一次完整的并發(fā)收集周期都會很長,回收到的內(nèi)存空間持續(xù)小于期間并發(fā)產(chǎn)生的浮動垃圾所占的空間,堆中剩余可騰挪的空間就越來越小了。目前唯一的辦法就是盡可能地增加堆容量大小,獲得更多喘息的時間。

2、吞吐量低于 G1 GC 一般來說,可能會下降 5%-15%。對于堆越小,這個效應(yīng)越明顯,堆非常大的時候,比如 100G,其他 GC 可能一次 Major 或 Full GC 要幾十秒以上,但是對于 ZGC 不需要那么大暫停。這種細粒度的優(yōu)化帶來的副作用就是,把很多環(huán)節(jié)其他 GC 里的 STW 整體處理,拆碎了,放到了更大時間范圍內(nèi)里去跟業(yè)務(wù)線程并發(fā)執(zhí)行,甚至?xí)苯幼寴I(yè)務(wù)線程幫忙做一些 GC 的操作,從而降低了業(yè)務(wù)線程的處理能力。

總結(jié)

綜上,其實 ZGC 并不是一個憑空冒出的全新垃圾回收,它結(jié)合前幾代 GC 的思想,同時在戰(zhàn)術(shù)上做了優(yōu)化以達到極限的 STW,ZGC 的優(yōu)秀表現(xiàn)有可能會改變未來程序編寫方式,站在垃圾收集器的角度,垃圾收集器特別喜歡不可變對象,原有編程方式鑒于內(nèi)存、GC 能力所限使用可變對象來復(fù)用對象而不是銷毀重建,試想如果有了 ZGC 的強大回收能力的加持,是不是我們就可以無腦的使用不可變對象進行代碼編寫






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 計數(shù)器
    +關(guān)注

    關(guān)注

    32

    文章

    2248

    瀏覽量

    94181
  • cms
    cms
    +關(guān)注

    關(guān)注

    0

    文章

    59

    瀏覽量

    10940
  • JVM
    JVM
    +關(guān)注

    關(guān)注

    0

    文章

    157

    瀏覽量

    12188
  • 虛擬機
    +關(guān)注

    關(guān)注

    1

    文章

    897

    瀏覽量

    27969
  • 收集器
    +關(guān)注

    關(guān)注

    0

    文章

    30

    瀏覽量

    3117

原文標題:從歷代GC算法角度刨析ZGC

文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    jvm的類加載器的整體結(jié)構(gòu)及過程解析

    前言 我們很多小伙伴平時都是做JAVA開發(fā)的,那么作為一名合格的工程師,你是否有仔細的思考過JVM的運行原理。 如果懂得了JVM的運行原理和內(nèi)存模型,像是一些JVM調(diào)優(yōu)、
    的頭像 發(fā)表于 09-27 15:49 ?3357次閱讀
    <b class='flag-5'>jvm</b>的類加載器的整體結(jié)構(gòu)及過程解析

    Jvm的整體結(jié)構(gòu)和特點

      一、虛擬機簡介  1、虛擬機概念  虛擬機(Virtual Machine)指通過軟件模擬的具有完整硬件系統(tǒng)功能的、運行在一個完全隔離環(huán)境中的完整計算機系統(tǒng)。在實體計算機中能夠完成的工作在虛擬機
    發(fā)表于 01-05 17:23

    Jvm垃圾回收機制及性能調(diào)優(yōu)實戰(zhàn)

    JVM中自動檢測并移除不再使用的數(shù)據(jù)對象的這種機制稱為:垃圾回收,簡稱GC。JVM通過使用垃圾收集器及使用相應(yīng)的
    發(fā)表于 04-03 14:31 ?2次下載

    帶顏色的JVM垃圾回收三色標記法

    三色標記法是一種垃圾回收法,它可以讓JVM不發(fā)生或僅短時間發(fā)生STW(Stop The World),從而達到清除JVM內(nèi)存垃圾的目的。JVM
    的頭像 發(fā)表于 10-20 14:23 ?1575次閱讀

    詳解JVM垃圾回收算法和垃圾回收器

    JVM 垃圾回收機制是對堆中沒有使用的對象進行回收,那么判斷對象是否“存活”就至關(guān)重要。在判斷對象是否“存活”的方法中,我們會介紹引用計數(shù)算法和可達性分析法。
    的頭像 發(fā)表于 03-29 13:55 ?1440次閱讀
    詳解<b class='flag-5'>JVM</b>的<b class='flag-5'>垃圾</b>回收算法和<b class='flag-5'>垃圾</b>回收器

    JVM內(nèi)存布局的多方面了解

      JVM內(nèi)存布局規(guī)定了Java在運行過程中內(nèi)存申請、分配、管理的策略,保證了JVM的穩(wěn)定高效運行。不同的JVM對于內(nèi)存的劃分方式和管理機制存在部分差異。結(jié)合
    發(fā)表于 07-08 15:09 ?388次閱讀

    JVM入門之垃圾回收算法

    根據(jù)如何判定對象是垃圾,垃圾回收算法分為兩類:1、 「引用計數(shù)式垃圾收集」 (判定垃圾是通過引用計數(shù)器)別名:直接垃圾收集 2、 「追蹤式
    的頭像 發(fā)表于 02-10 11:40 ?736次閱讀
    <b class='flag-5'>JVM</b>入門之<b class='flag-5'>垃圾</b>回收算法

    JVM內(nèi)存布局詳解

    JVM內(nèi)存布局規(guī)定了Java在運行過程中內(nèi)存申請、分配、管理的策略,保證了JVM的穩(wěn)定高效運行。不同的JVM對于內(nèi)存的劃分方式和管理機制存在部分差異。結(jié)合
    的頭像 發(fā)表于 04-26 10:10 ?481次閱讀
    <b class='flag-5'>JVM</b>內(nèi)存布局詳解

    詳細解析JVM中的垃圾回收機制

    Java語言的一大優(yōu)勢在于其具有自動垃圾回收(Garbage Collection,GC)機制,讓開發(fā)者無需關(guān)心內(nèi)存的分配與釋放。
    的頭像 發(fā)表于 06-06 16:53 ?1921次閱讀

    垃圾收集器的JVM參數(shù)配置

    本篇文章我們就來給大家介紹垃圾收集器的 JVM 參數(shù)配置。 JVM參數(shù)有很多,其實我們直接使用默認的JVM參數(shù),不去修改都可以滿足大多數(shù)情況。但是如果你想在有限的硬件資源下,部署的系統(tǒng)
    的頭像 發(fā)表于 10-09 16:35 ?502次閱讀
    <b class='flag-5'>垃圾</b>收集器的<b class='flag-5'>JVM</b>參數(shù)配置

    jvm調(diào)優(yōu)參數(shù)

    JVM(Java虛擬機)是Java程序的運行環(huán)境,它負責(zé)解釋Java字節(jié)碼并執(zhí)行相應(yīng)的指令。為了提高應(yīng)用程序的性能和穩(wěn)定性,我們可以調(diào)優(yōu)JVM的參數(shù)。 JVM調(diào)優(yōu)主要涉及到堆內(nèi)存、垃圾
    的頭像 發(fā)表于 12-05 11:29 ?555次閱讀

    jvm參數(shù)的設(shè)置和jvm調(diào)優(yōu)

    JVM(Java虛擬機)參數(shù)的設(shè)置和調(diào)優(yōu)對于提高Java應(yīng)用程序的性能和穩(wěn)定性非常重要。在本文中,我們將詳細介紹JVM參數(shù)的設(shè)置和調(diào)優(yōu)方法。 一、JVM參數(shù)的設(shè)置 內(nèi)存參數(shù): -Xms:設(shè)置J
    的頭像 發(fā)表于 12-05 11:36 ?1253次閱讀

    jvm配置的mx

    用于設(shè)置JVM的最大堆內(nèi)存大小,即堆的上限。當堆內(nèi)存不足時,JVM會觸發(fā)垃圾回收機制以釋放內(nèi)存。如果垃圾回收無法釋放足夠的內(nèi)存,
    的頭像 發(fā)表于 12-05 14:24 ?634次閱讀

    weblogic jvm參數(shù)配置

    在WebLogic中,JVM參數(shù)配置是非常重要的,它可以對應(yīng)用程序的性能和穩(wěn)定性產(chǎn)生直接影響。JVM參數(shù)通過調(diào)整Java虛擬機的運行時行為,可以優(yōu)化內(nèi)存管理、垃圾回收以及線程管理等方面的性能。 首先
    的頭像 發(fā)表于 12-05 14:31 ?1242次閱讀

    從原理聊JVM(一):染色標記和垃圾回收算法

    導(dǎo)讀 JAVA簡單易用的特性,能夠讓研發(fā)人員在不了解JVM的底層運行機制的情況下依舊能夠編寫出功能完善的代碼。 但是對JVM的理解,是一個程序員普通和優(yōu)秀的分水嶺。全面地了解JVM
    的頭像 發(fā)表于 08-20 15:25 ?140次閱讀
    從原理聊<b class='flag-5'>JVM</b>(一):染色標記和<b class='flag-5'>垃圾</b>回收算法