前言
本文所有介紹僅限于HotSpot虛擬機(jī), 本文先介紹了垃圾回收的必要手段,基于這些手段講解了歷代垃圾回收算法是如何工作的, 每一種算法不會(huì)講的特別詳細(xì),只為讀者從算法角度理解工作原理,從而引出ZGC,方便讀者循序漸進(jìn)地了解。GC 是 Garbage Collection 的縮寫,顧名思義垃圾回收機(jī)制,即當(dāng)需要分配的內(nèi)存空間不再使用的時(shí)候,JVM 將調(diào)用垃圾回收機(jī)制來(lái)回收內(nèi)存空間。
那么 JVM 的垃圾機(jī)制是如何工作的呢? 第一步識(shí)別出哪些空間不再使用(識(shí)別并標(biāo)記出哪些對(duì)象已死); 第二步回收不再使用空間(清除已死對(duì)象 )
判斷對(duì)象是否已死
判斷對(duì)象是否已死通常有兩種方式,引用計(jì)數(shù)法和可達(dá)性分析法
引用計(jì)數(shù)法
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加 1: 當(dāng)引用失效時(shí),計(jì)數(shù)器值就減 1; 任何時(shí)刻計(jì)數(shù)器為 0 的對(duì)象就是不能再被使用的。 簡(jiǎn)單高效,但無(wú)法解決循環(huán)引用問(wèn)題,a=b,b=a 引用計(jì)數(shù)法并沒(méi)有在產(chǎn)品級(jí)的 JVM 中得到應(yīng)用
可達(dá)性分析法
這個(gè)算法的基本思路就是通過(guò)一系列的稱為 “GC Roots” 的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱為引用鏈 ( Reference Chain), 當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連 (用圖論的話來(lái)說(shuō),就是從 GC Roots 到這個(gè)對(duì)象不可達(dá)) 時(shí),則證明此對(duì)象是不可用的。
?
不過(guò)可達(dá)性算法中的對(duì)象并不是立即死亡的,對(duì)象擁有一次自我拯救的機(jī)會(huì),對(duì)象被系統(tǒng)宣告死亡至少要經(jīng)歷兩次標(biāo)記過(guò)程,第一次是經(jīng)過(guò)可達(dá)性分析之后沒(méi)有與 GC Roots 相連的引用鏈,第二次是在由虛擬機(jī)自動(dòng)建立的Finalize隊(duì)列中判斷是否需要執(zhí)行 finalize () 方法。 HotSopt 虛擬機(jī)采用該算法。
清除已死對(duì)象的方式
標(biāo)記清除算法
先標(biāo)記再清除 不足:1 效率問(wèn)題,標(biāo)記和清除效率都不高。2 空間問(wèn)題,產(chǎn)生大量空間碎片
復(fù)制算法
內(nèi)存分兩塊,A,B A 用完了,將存活對(duì)象拷貝到 B,A 清理掉 代價(jià):內(nèi)存少了一半。 HotSopt 虛擬機(jī)用此算法回收新生代。將新生代內(nèi)存劃分為 8:1:1 的 Eden 和 Survivor 解決復(fù)制算法內(nèi)存使用率低的問(wèn)題 ???
標(biāo)記整理算法
老年代使用,方式和標(biāo)記清除類似,只是不直接清除,而是將后續(xù)對(duì)象向一端移動(dòng),并清理掉邊界以外的內(nèi)存。 ?
分代收集算法
分代收集是一個(gè)算法方案,整合了以上算法的優(yōu)點(diǎn),一般是把 Java 堆分為新生代和老年代,在新生代中,使用復(fù)制算法老年代 “標(biāo)記一清理” 或者 “標(biāo)記一整理”
歷代垃圾收集器簡(jiǎn)介
通過(guò)上文我們了解了怎樣識(shí)別垃圾,怎樣清理垃圾,接下來(lái),講 ZGC 之前,我們回顧一下歷代垃圾回收是怎樣做的,主要是想給讀者一種歷史的視角,任何技術(shù)都不是憑空產(chǎn)生的,更多的是在前人成果之上進(jìn)行優(yōu)化整合 我們先看一個(gè)歷代 JDK 垃圾收集器對(duì)比表格,以下表格著重說(shuō)明或引出幾個(gè)問(wèn)題: 1 CMS 從來(lái)未被當(dāng)作默認(rèn) GC,且已廢棄 2 CMS 的思想其實(shí)部分被 ZGC 吸收,CMS 已死,但他的魂還在 3 JDK11、JDK17 為長(zhǎng)期迭代版本,項(xiàng)目中應(yīng)優(yōu)先使用這兩個(gè)版本
版本 | 發(fā)布時(shí)間 | 默認(rèn)收集器 | 事件 |
jdk1.3 | 2000-05-08 | serial | |
jdk1.4 | 2004-02-06 | ParNew | |
jdk1.5/5.0 | 2004-09-30 | Parallel Scavenge/serial | CMS 登場(chǎng) |
jdk1.6/6.0 | 2006-12-11 | Parallel Scavenge/Parallel Old | |
dk1.7/7.0 | 2011-07-28 | Parallel Scavenge/Parallel Old | G1 登場(chǎng) |
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 登場(chǎng) |
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)常在各種場(chǎng)景聽(tīng)到以下幾種 GC 名詞,Young GC、Old GC、Mixed GC、Full GC、Major GC、Minor GC,他們到底什么意思,本人進(jìn)行了以下梳理 首先 GC 分兩類,Partial GC(部分回收),F(xiàn)ull GC Partial GC:并不收集整個(gè) GC 堆的模式,以下全是 Partial GC 的子集 Young GC:只收集 young gen 的 GC Old GC:只收集 old gen 的 GC。
只有 CMS 的 concurrent collection 是這個(gè)模式 Mixed GC:只有 G1 有這個(gè)模式,收集整個(gè) young gen 以及部分 old gen 的 GC。
Minor GC:只有 G1 有這個(gè)模式,收集整個(gè) young gen Full GC:收集整個(gè)堆,包括 young gen、old gen、perm gen(如果存在的話)等所有部分的模式。
Major GC:通常是跟 full GC 是等價(jià)的
serial 收集器
單線程收集器,“單線程” 的意義并不僅僅說(shuō)明它只會(huì)使用一個(gè) CPU 或一條收集線程去完成垃圾收集工作,更重要的是在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程, 直到它收集結(jié)束。它依然是虛擬機(jī)運(yùn)行在 Client 模式下的默認(rèn)新生代收集器。
它也有著優(yōu)于其他收集器的地方:簡(jiǎn)單而高效 (與其他收集器的單線程比), 對(duì)于限定單個(gè) CPU 的環(huán)境來(lái)說(shuō),Serial I 收集器由于沒(méi)有線程交互的開(kāi)銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。 下圖彩色部分說(shuō)明了它的算法,簡(jiǎn)單粗暴 1 停止用戶線程 2 單線程垃圾回收新生代 3 重啟用戶線程 ??
ParNew 收集器
Parnew 收集器其實(shí)就是 Serial l 收集器的多線程版本。它是許多運(yùn)行在 Server 模式下的虛擬機(jī)中首選的新生代收集器,其中有一個(gè)與性能無(wú)關(guān)但很重要的原因是,除了 Serial 收集器外,目前只有它能與 CMS 收集器配合工作。Pardew 收集器在單 CPU 的環(huán)境中絕對(duì)不會(huì)有比 Serial 收集器更好的效果。
它默認(rèn)開(kāi)啟的收集線程數(shù)與 CPU 的數(shù)量相同,在 CPU 非常多 (臂如 32 個(gè)) 的環(huán)境下,可以使用 - XX: ParallelGCThreads 參數(shù)來(lái)限制垃圾收集的線程數(shù)。
ParNew 收集器追求降低 GC 時(shí)用戶線程的停頓時(shí)間,適合交互式應(yīng)用,良好的反應(yīng)速度提升用戶體驗(yàn). 下圖彩色部分說(shuō)明了它的算法,同樣簡(jiǎn)單粗暴 1 停止用戶線程 2 多線程垃圾回收新生代 3 重啟用戶線程
Parallel Scavenge 收集器
Parallel Scavenge 收集器是一個(gè)新生代收集器,它也是使用復(fù)制算法的收集器,又是并行的多線程收集器。算法的角度它和 ParNew 一樣,在此就不畫圖解釋了 Parallel Scavenge 收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量( Throughput) 吞吐量是指用戶線程運(yùn)行時(shí)間占 CPU 總時(shí)間的比例 通過(guò)以下兩種方式可達(dá)到目的: 1. 在多 CPU 環(huán)境中使用多條 GC 線程,從而垃圾回收的時(shí)間減少,從而用戶線程停頓的時(shí)間也減少; 2. 實(shí)現(xiàn) GC 線程與用戶線程并發(fā)執(zhí)行。
Serial Old 收集器
Serial Old 是 Serial 收集器的老年代版本,它同樣是一個(gè)單線程收集器,使用 “標(biāo)記整理” 算法。這個(gè)收集器的主要意義也是在于給 Client 模式下的虛擬機(jī)使用。 如果在 Server 模式下,那么它主要還有兩大用途: 一種用途是在 JDK1.5 以及之前的版本中與 ParallelScavenge 收集器搭配使用, 另一種用途就是作為 CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生 Concurrent Mode Failure 時(shí)使用 下圖彩色部分說(shuō)明了它的算法,同樣簡(jiǎn)單粗暴 1 停止用戶線程 2 單線程垃圾回收老年代 3 重啟用戶線程
??
Parallel Old 收集器
Paralle Old 是 Parallel Scavenge 收集器的老年代版本,一般它們搭配使用,追求 CPU 吞吐量,使用多線程和 “標(biāo)記一整理” 算法。 下圖彩色部分說(shuō)明了它的算法,同樣簡(jiǎn)單粗暴 1 停止用戶線程 2 多線程垃圾回收老年代 3 重啟用戶線程
CMS 收集器
以上 5 種垃圾回收原理不難理解,算法之所以如此簡(jiǎn)單個(gè)人理解在當(dāng)時(shí)使用這種算法就夠了,隨著 JAVA 的攻城略地,有一種垃圾回收需求出現(xiàn),即使用盡量短的回收停頓時(shí)間,以避免過(guò)久的影響用戶線程,CMS 登場(chǎng)了。 CMS (Concurrent Mark Sweep) 收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。
想要達(dá)到目的,就要分析 GC 時(shí)最占用時(shí)間的是什么操作,比較浪費(fèi)時(shí)間的是標(biāo)記已死對(duì)象、清除對(duì)象,那么如果可以和用戶線程并發(fā)的進(jìn)行,GC 的停頓基本就限制在了標(biāo)記所花費(fèi)的時(shí)間。
?
如上圖,CMS 收集器是基于 “標(biāo)記一清除” 法實(shí)現(xiàn)的,它的運(yùn)作過(guò)程分為 4 個(gè)步驟
?初始標(biāo)記 (EMS initial mark) stop the world
?并發(fā)標(biāo)記 (CMS concurrent mark)
?重新標(biāo)記 (CMS remark) stop the world
?并發(fā)清除 (CMS concurrent sweep)
初始標(biāo)記的作用是查找 GC Roots 集合的過(guò)程,這個(gè)過(guò)程處理對(duì)象相對(duì)較少,速度很快。(為什么要進(jìn)行初始標(biāo)記:枚舉根結(jié)點(diǎn)。并發(fā)標(biāo)記是實(shí)際標(biāo)記所有對(duì)象是否已死的過(guò)程,比較耗時(shí),所以采用并發(fā)的方式。
重新標(biāo)記主要是處理并發(fā)標(biāo)記期間所產(chǎn)生的新的垃圾。
重新標(biāo)記階段不需要再重新標(biāo)記所有對(duì)象,只對(duì)并發(fā)標(biāo)記階段改動(dòng)過(guò)的對(duì)象做標(biāo)記即可。 優(yōu)點(diǎn): 并發(fā)收集、低停頓 缺點(diǎn): CMS 收集器對(duì) CPU 資源非常敏感。
CMS 收集器無(wú)法處理浮動(dòng)垃圾 (Floating Garbage), 可能出現(xiàn) “Concurrent ModeFailure” 失敗而導(dǎo)致另一次 Full GC 的產(chǎn)生。
“標(biāo)記一清除” 法導(dǎo)致大量空間碎片產(chǎn)生,以至于老年代還有大量空間,卻沒(méi)有整塊空間存儲(chǔ)某對(duì)象。
Concurrent ModeFailure可能原因及方案 原因1:CMS觸發(fā)太晚 方案:將-XX:CMSInitiatingOccupancyFraction=N調(diào)小 (達(dá)到百分比進(jìn)行垃圾回收); 原因2:空間碎片太多 方案:開(kāi)啟空間碎片整理,并將空間碎片整理周期設(shè)置在合理范圍; -XX:+UseCMSCompactAtFullCollection (空間碎片整理) -XX:CMSFullGCsBeforeCompaction=n 原因3:垃圾產(chǎn)生速度超過(guò)清理速度 晉升閾值過(guò)小; Survivor空間過(guò)小,導(dǎo)致溢出; Eden區(qū)過(guò)小,導(dǎo)致晉升速率提高;存在大對(duì)象;
G1 收集器
G1 是一款面向服務(wù)端應(yīng)用的垃圾收集器。下文會(huì)簡(jiǎn)單講解一下它的 “特點(diǎn)” 和 “內(nèi)存分配與回收策略”,有基礎(chǔ)或不感興趣的同學(xué)直接跳到 “G1 垃圾回收流程”
特點(diǎn)
并行與并發(fā) G1 能充分利用多 CPU、多核環(huán)境下的硬件優(yōu)勢(shì),使用多個(gè) CPU (CPU 或者 CPU 核心) 來(lái)縮短 Stop-The- World 停頓的時(shí)間,部分其他收集器原本需要停頓 Java 線程執(zhí)行的 GC 動(dòng)作,G1 收集器仍然可以通過(guò)并發(fā)的方式讓 Java 程序繼續(xù)執(zhí)行。
分代收集 與其他收集器一樣,分代概念在 G1 中依然得以保留。雖然 G1 可以不需要其他收集器配合就能獨(dú)立管理整個(gè) GC 堆,但它能夠采用不同的方式去處理新創(chuàng)建的對(duì)象和已經(jīng)存活了一段時(shí)間、熬過(guò)多次 GC 的舊對(duì)象以獲取更好的收集效果。
空間整合 與 CMS 的 “標(biāo)記一清理” 算法不同,G1 從整體來(lái)看是基于 “標(biāo)記一整理” 算法實(shí)現(xiàn)的收集器,從局部 (兩個(gè) Region 之間) 上來(lái)看是基于 “復(fù)制” 算法實(shí)現(xiàn)的,但無(wú)論如何,這兩種算法都意味著 G1 運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片,收集后能提供規(guī)整的可用內(nèi)存。這種特性有利于程序長(zhǎng)時(shí)間運(yùn)行,分配大對(duì)象時(shí)不會(huì)因?yàn)闊o(wú)法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次 GC。
可預(yù)測(cè)的停頓 這是 G1 相對(duì)于 CMS 的另一大優(yōu)勢(shì),降低停頓時(shí)間是 G1 和 CMS 共同的關(guān)注點(diǎn),但 G1 除了追求低停頓外,還能建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長(zhǎng)度為 M 毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過(guò) N 毫秒,這幾乎已經(jīng)是實(shí)時(shí) Java (RTSJ) 的垃圾收集器的特征了。
在 G1 之前的其他收集器進(jìn)行收集的范圍都是整個(gè)新生代或者老年代,而 G1 不再是這樣。使用 G1 收集器時(shí),Java 堆的內(nèi)存布局就與其他收集器有很大差別,它將整個(gè) Java 堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域 (Region), 雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分 Region (不需要連續(xù)) 的集合
內(nèi)存分配與回收策略
對(duì)象優(yōu)先在 Eden 分配 大多數(shù)情況下,對(duì)象在新生代 Eden 區(qū)中分配。當(dāng) Eden 區(qū)沒(méi)有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次 Minor[?ma?n?(r)] GC 大對(duì)象直接進(jìn)入老年代 所謂的大對(duì)象是指,需要大量連續(xù)內(nèi)存空間的 Java 對(duì)象,最典型的大對(duì)象就是那種很長(zhǎng)的字符串以及數(shù)組。
大對(duì)象對(duì)虛擬機(jī)的內(nèi)存分配來(lái)說(shuō)就是一個(gè)壞消息 (比遇到一個(gè)大對(duì)象更加壞的消息就是遇到一群 “朝生夕滅” 的 “短命大對(duì)象” 寫程序的時(shí)候應(yīng)當(dāng)避免), 經(jīng)常出現(xiàn)大對(duì)象容易導(dǎo)致內(nèi)存還有不少空間時(shí)就提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間來(lái) “安置” 它們。
長(zhǎng)期存活的對(duì)象將進(jìn)入老年代 虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡 (Age) 計(jì)數(shù)器。如果對(duì)象在 Eden 出生并經(jīng)過(guò)第一次 Minor GC 后仍然存活,并且能被 Survivor 容納的話,將被移動(dòng)到 Survivor 空間中,并且對(duì)象年齡設(shè)為 1。對(duì)象在 Survivor 區(qū)中每 “熬過(guò)” 一次 Minor GC, 年齡就增加 1 歲,當(dāng)它的年齡增加到一定程度(默認(rèn) 15 歲)會(huì)被晉升到老年代中。
對(duì)象晉升老年代的年齡閾值,可以通過(guò)參數(shù)據(jù) - XX : MaxTenuringThreshold 設(shè)置 動(dòng)態(tài)對(duì)象年齡判定 為了能更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機(jī)并不是水遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了 MaxTenuringThreshold 才能晉升老年代,如果在 Survivor 空間中相同年齡所有對(duì)象大小的總和大于 Survivor 空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須等到 MaxTenuringThreshold 中要求的年齡。
空間分配擔(dān)保 在發(fā)生 Minor GC 之前,虛擬機(jī)會(huì)先檢査老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那么 Minor GC 可以確保是安全的。如果不成立,則虛擬機(jī)會(huì)查看 HandlePromotionFailure 設(shè)置值是否允許擔(dān)保失敗。
如果允許,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將嘗試著進(jìn)行一次 Minor GC, 盡管這次 Minor GC 是有風(fēng)險(xiǎn)的;如果小于,或者 HandlePromotionFailure 設(shè)置不允許冒險(xiǎn),那這時(shí)也要改為進(jìn)行一次 Full GC. 為什么要擔(dān)保: Minor GC 后還有大量對(duì)象存活且空間不夠存放新對(duì)象,就要直接在老年代存放 為什么是歷次晉升到老年代對(duì)象的平均大小: 取平均值進(jìn)行比較其實(shí)仍然是一種動(dòng)態(tài)概率的手段,也就是說(shuō),如果某次 Minor GCd 存活后的對(duì)象突增,遠(yuǎn)遠(yuǎn)高于平均值的話,依然會(huì)導(dǎo)致?lián)J?(HandlePromotionFailure) 如果出現(xiàn)了 HandlePromotionFailure 失敗,那就只好在失敗后重新發(fā)起一次 Full GC。
雖然擔(dān)保失敗時(shí)繞的子是最大的,但大部分情況下都還是會(huì)將 HandlePromotionFailure 開(kāi)關(guān)打開(kāi),避免 Full GC 過(guò)于頻繁。 eden 的大小范圍默認(rèn)是 =【-XX:G1NewSizePercent,-XX:G1MaxNewSizePercent】=【整堆 5%,整堆 60%】 humongous 如果一個(gè)對(duì)象的大小已經(jīng)超過(guò) Region 大小的 50% 了,那么就會(huì)被放入大對(duì)象專門的 Region 中,這種 Region 我們叫 humongous
G1 垃圾回收流程
??
網(wǎng)上對(duì) G1 的回收階段有不同的說(shuō)法,參考 Oracle JVM 工程師的一個(gè)說(shuō)法: 他把整個(gè) G1 的垃圾回收階段分成了這么三個(gè),第一個(gè)叫 Minor GC,就是對(duì)新生代的垃圾收集,第二個(gè)階段呢叫 Minor GC + Concurrent Mark,就是新生代的垃圾收集同時(shí)呢會(huì)執(zhí)行一些并發(fā)的標(biāo)記,這是第二個(gè)階段,第三個(gè)階段呢它叫 Mixed GC 混合收集,這三個(gè)階段是一個(gè)循環(huán)的過(guò)程。
剛開(kāi)始是這個(gè)新生代的垃圾收集,經(jīng)過(guò)一段時(shí)間,當(dāng)老年代的內(nèi)存超過(guò)一個(gè)閾值了,它會(huì)在新生代垃圾收集的同時(shí)進(jìn)行并發(fā)的標(biāo)記,等這個(gè)階段完成了以后,它會(huì)進(jìn)行一個(gè)混合收集,混合收集就是會(huì)對(duì)新生代、幸存區(qū)還有老年代都來(lái)進(jìn)行一個(gè)規(guī)模較大的一次收集,等內(nèi)存釋放掉了,混合收集結(jié)束。這時(shí)候伊甸園的內(nèi)存都被釋放掉,它會(huì)再次進(jìn)入新生代的一個(gè)垃圾收集過(guò)程,那我們先來(lái)看看這個(gè)新生代的收集 Minor GC。
Minor GC 的回收過(guò)程(eden 滿了回收)
選定所有 Eden Region 放入 CSet,使用多線程復(fù)制算法將 CSet 的存活對(duì)象復(fù)制到 Survivor Region 或者晉升到 Old Region。 下圖分 7 步演示了這個(gè)過(guò)程 1 初始狀態(tài),堆無(wú)占用 2 Eden Region 滿了進(jìn)行標(biāo)記 3 將存活對(duì)象復(fù)制到 Survivor Region 4 清理 Eden Region 5 Eden Region 又滿了進(jìn)行再次標(biāo)記,此時(shí)會(huì)連帶 Survivor Region 一起標(biāo)記 6 將存活對(duì)象復(fù)制到另一個(gè) Survivor Region 7 再次清理 Eden Region 和被標(biāo)記過(guò)的 Survivor Region ? Minor GC 結(jié)束后自動(dòng)進(jìn)行并發(fā)標(biāo)記,為以后可能的 Mixed GC 做準(zhǔn)備
Mixed GC 的回收過(guò)程(專注垃圾最多的分區(qū))
選定所有 Eden Region 和全局并發(fā)標(biāo)記計(jì)算得到的收益較高的部分 Old Region 放入 CSet,使用多線程復(fù)制算法將 CSet 的存活對(duì)象復(fù)制到 Survivor Region 或者晉升到 Old Region。 當(dāng)堆空間的占用率達(dá)到一定閾值后會(huì)觸發(fā) Mixed GC(默認(rèn) 45%,由參數(shù)決定) Mixed GC 它一定會(huì)回收年輕代,并會(huì)采集部分老年代的 Region 進(jìn)行回收的,所以它是一個(gè) “混合” GC。 下圖分 3 步演示了這個(gè)過(guò)程 1 并發(fā)標(biāo)記所有 Region 2 并發(fā)復(fù)制 3 并發(fā)清理 ??
ZGC
ZGC(Z Garbage Collector) 是一款性能比 G1 更加優(yōu)秀的垃圾收集器。ZGC 第一次出現(xiàn)是在 JDK 11 中以實(shí)驗(yàn)性的特性引入,這也是 JDK 11 中最大的亮點(diǎn)。在 JDK 15 中 ZGC 不再是實(shí)驗(yàn)功能,可以正式投入生產(chǎn)使用了。
目標(biāo)低延遲
?保證最大停頓時(shí)間在幾毫秒之內(nèi),不管你堆多大或者存活的對(duì)象有多少。
?可以處理 8MB-16TB 的堆
通過(guò)以上歷代垃圾回收器的講解,我們大致了解到減少延遲的底層思想不外乎將 stop the world 進(jìn)行極限壓縮,將能并行的部分全部采用和用戶線程并行的方式處理,然而 ZGC 更 "過(guò)分" 它甚至把一分部垃圾回收的工作交給了用戶線程去做,那么它是怎么做到的呢?ZGC 的標(biāo)記和清理工作同 CMS、G1 大致差不多,仔細(xì)看下圖的過(guò)程,和 CMS 特別像,這就是我在上文說(shuō)的 CMS 其實(shí)并沒(méi)有真正被拋棄,它的部分思想在 ZGC 有發(fā)揚(yáng)。
??
ZGC 的步驟大致可分為三大階段分別是標(biāo)記、轉(zhuǎn)移、重定位。 標(biāo)記:從根開(kāi)始標(biāo)記所有存活對(duì)象 轉(zhuǎn)移:選擇部分活躍對(duì)象轉(zhuǎn)移到新的內(nèi)存空間上 重定位:因?yàn)閷?duì)象地址變了,所以之前指向老對(duì)象的指針都要換到新對(duì)象地址上。 并且這三個(gè)階段都是并發(fā)的。
初始轉(zhuǎn)移需要掃描 GC Roots 直接引用的對(duì)象并進(jìn)行轉(zhuǎn)移,這個(gè)過(guò)程需要 STW,STW 時(shí)間跟 GC Roots 成正比。 并發(fā)轉(zhuǎn)移準(zhǔn)備 :分析最有回收價(jià)值 GC 分頁(yè)(無(wú) STW) 初始轉(zhuǎn)移應(yīng)對(duì)初始標(biāo)記的數(shù)據(jù) 并發(fā)轉(zhuǎn)移應(yīng)對(duì)并發(fā)標(biāo)記的數(shù)據(jù) 除了標(biāo)記清理過(guò)程繼承了 CMS 和 G1 的思想,ZGC 要做了以下優(yōu)化
并發(fā)清理(轉(zhuǎn)移對(duì)象)
在 CMS 和 G1 中都用到了寫屏障,而 ZGC 用到了讀屏障。 寫屏障是在對(duì)象引用賦值時(shí)候的 AOP,而讀屏障是在讀取引用時(shí)的 AOP。 比如Object a = obj.foo;,這個(gè)過(guò)程就會(huì)觸發(fā)讀屏障。
也正是用了讀屏障,ZGC 可以并發(fā)轉(zhuǎn)移對(duì)象,而 G1 用的是寫屏障,所以轉(zhuǎn)移對(duì)象時(shí)候只能 STW。 簡(jiǎn)單的說(shuō)就是 GC 線程轉(zhuǎn)移對(duì)象之后,應(yīng)用線程讀取對(duì)象時(shí),可以利用讀屏障通過(guò)指針上的標(biāo)志來(lái)判斷對(duì)象是否被轉(zhuǎn)移。
讀屏障會(huì)對(duì)應(yīng)用程序的性能有一定影響,據(jù)測(cè)試,對(duì)性能的最高影響達(dá)到 4%,但提高了 GC 并發(fā)能力,降低了 STW。這就是上面所說(shuō)的 ZGC “過(guò)分” 地將部分垃圾回收工作交給用戶線程的原因。
染色指針
染色指針其實(shí)就是從 64 位的指針中,拿幾位來(lái)標(biāo)識(shí)對(duì)象此時(shí)的情況,分別表示 Marked0、Marked1、Remapped、Finalizable。 ?
0-41 這 42 位就是正常的地址,所以說(shuō) ZGC 最大支持 4TB (理論上可以 16TB) 的內(nèi)存,因?yàn)榫?42 位用來(lái)表示地址 也因此 ZGC 不支持 32 位指針,也不支持指針壓縮。
其實(shí)對(duì)象只需要兩個(gè)狀態(tài) Marked,Remapped,對(duì)象被標(biāo)記了,對(duì)象被重新映射了,為什么會(huì)有 M0,M1,用來(lái)區(qū)分本次 GC 標(biāo)記和上次 GC 標(biāo)記 以下是標(biāo)記轉(zhuǎn)移算法說(shuō)明: 1 在垃圾回收開(kāi)始前:Remapped 2 標(biāo)記過(guò)程: 標(biāo)記線程訪問(wèn) 發(fā)現(xiàn)對(duì)象地址視圖是 Remapped 這時(shí)候?qū)⒅羔槝?biāo)記為 M0 發(fā)現(xiàn)對(duì)象地址視圖是 M0,則說(shuō)明這個(gè)對(duì)象是標(biāo)記開(kāi)始之后新分配的或者已經(jīng)標(biāo)記過(guò)的對(duì)象,所以無(wú)需處理 應(yīng)用線程 如果創(chuàng)建新對(duì)象,則將其地址視圖置為 M0 3 標(biāo)記階段結(jié)束后 ZGC 會(huì)使用一個(gè)對(duì)象活躍表來(lái)存儲(chǔ)這些對(duì)象地址,此時(shí)活躍的對(duì)象地址視圖是 M0 4 并發(fā)轉(zhuǎn)移階段 轉(zhuǎn)移線程: 轉(zhuǎn)移成功后對(duì)象地址視圖被置為 Remapped(也就是說(shuō) GC 線程如果訪問(wèn)到對(duì)象,此時(shí)對(duì)象地址視圖是 M0,并且存在或活躍表中,則將其轉(zhuǎn)移,并將地址視圖置為 Remapped ) 如果在活躍表中,但是地址視圖已經(jīng)是 Remapped 說(shuō)明已經(jīng)被轉(zhuǎn)移了,不做處理。
應(yīng)用線程: 如果創(chuàng)建新對(duì)象,地址視圖會(huì)設(shè)為 Remapped 5 下次標(biāo)記使用 M1 M1 標(biāo)識(shí)本次垃圾回收中活躍的對(duì)象 M0 是上一次回收被標(biāo)記的對(duì)象,但是沒(méi)有被轉(zhuǎn)移,且在本次回收中也沒(méi)有被標(biāo)記活躍的對(duì)象。
下圖展示了 Marked,Remapped 的過(guò)程, 初始化時(shí) A,B,C 三個(gè)對(duì)象處于 Remapped 狀態(tài) 第一次 GC,A 被轉(zhuǎn)移,B 未被轉(zhuǎn)移,C 無(wú)引用將被回收 第二次 GC,由于 A 被轉(zhuǎn)移過(guò)了(Remapped 狀態(tài)),所以被標(biāo)記 M1,此時(shí)恰好 B 為不活躍對(duì)象,將被清理 第三次 GC,A 又被標(biāo)記成 M0 ??
多重映射
Marked0、Marked1 和 Remapped 三個(gè)視圖 ZGC 為了能高效、靈活地管理內(nèi)存,實(shí)現(xiàn)了兩級(jí)內(nèi)存管理:虛擬內(nèi)存和物理內(nèi)存,并且實(shí)現(xiàn)了物理內(nèi)存和虛擬內(nèi)存的映射關(guān)系 在 ZGC 中這三個(gè)空間在同一時(shí)間點(diǎn)有且僅有一個(gè)空間有效,利用虛擬空間換時(shí)間,這三個(gè)空間的切換是由垃圾回收的不同階段觸發(fā)的,通過(guò)限定三個(gè)空間在同一時(shí)間點(diǎn)有且僅有一個(gè)空間有效高效的完成 GC 過(guò)程的并發(fā)操作 ?
支持 NUMA
NUMA 是非一致內(nèi)存訪問(wèn)的縮寫 (Non-Uniform Memory Access,NUMA) 早年如下圖:SMP 架構(gòu) (Symmetric Multi-Processor),因?yàn)槿我粋€(gè) CPU 對(duì)內(nèi)存的訪問(wèn)速度是一致的,不用考慮不同內(nèi)存地址之間的差異,所以也稱一致內(nèi)存訪問(wèn)(Uniform Memory Access, UMA )。這個(gè)核心越加越多,漸漸的總線和北橋就成為瓶頸,那不能夠啊,于是就想了個(gè)辦法。 ? ??
把 CPU 和內(nèi)存集成到一個(gè)單元上,這個(gè)就是非一致內(nèi)存訪問(wèn) (Non-Uniform Memory Access,NUMA)。
?ZGC 對(duì) NUMA 的支持是小分區(qū)分配時(shí)會(huì)優(yōu)先從本地內(nèi)存分配,如果本地內(nèi)存不足則從遠(yuǎn)程內(nèi)存分配。
ZGC 優(yōu)劣
綜上分析,ZGC 在戰(zhàn)略上沿用了上幾代 GC 的算法策略,采用并發(fā)標(biāo)記,并發(fā)清理的思路,在戰(zhàn)術(shù)上,通過(guò)染色指針、多重映射,讀屏障等優(yōu)化達(dá)到更理想的并發(fā)清理,通過(guò)支持 NUMA 達(dá)到了更快的內(nèi)存操作。但 ZGC 同樣不是銀彈,它也有自身的優(yōu)缺點(diǎn),如下
優(yōu)勢(shì):
1、一旦某個(gè) Region 的存活對(duì)象被移走之后,這個(gè) Region 立即就能夠被釋放和重用掉,而不必等待整個(gè)堆中所有指向該 Region 的引用都被修正后才能清理,這使得理論上只要還有一個(gè)空閑 Region,ZGC 就能完成收集。
2、顏色指針可以大幅減少在垃圾收集過(guò)程中內(nèi)存屏障的使用數(shù)量,ZGC 只使用了讀屏障。
3、顏色指針具備強(qiáng)大的擴(kuò)展性,它可以作為一種可擴(kuò)展的存儲(chǔ)結(jié)構(gòu)用來(lái)記錄更多與對(duì)象標(biāo)記、重定位過(guò)程相關(guān)的數(shù)據(jù),以便日后進(jìn)一步提高性能。
劣勢(shì):
1、它能承受的對(duì)象分配速率不會(huì)太高 ZGC 準(zhǔn)備要對(duì)一個(gè)很大的堆做一次完整的并發(fā)收集。在這段時(shí)間里面,由于應(yīng)用的對(duì)象分配速率很高,將創(chuàng)造大量的新對(duì)象,這些新對(duì)象很難進(jìn)入當(dāng)次收集的標(biāo)記范圍,通常就只能全部當(dāng)作存活對(duì)象來(lái)看待 —— 盡管其中絕大部分對(duì)象都是朝生夕滅的,這就產(chǎn)生了大量的浮動(dòng)垃圾。
如果這種高速分配持續(xù)維持的話,每一次完整的并發(fā)收集周期都會(huì)很長(zhǎng),回收到的內(nèi)存空間持續(xù)小于期間并發(fā)產(chǎn)生的浮動(dòng)垃圾所占的空間,堆中剩余可騰挪的空間就越來(lái)越小了。目前唯一的辦法就是盡可能地增加堆容量大小,獲得更多喘息的時(shí)間。
2、吞吐量低于 G1 GC 一般來(lái)說(shuō),可能會(huì)下降 5%-15%。對(duì)于堆越小,這個(gè)效應(yīng)越明顯,堆非常大的時(shí)候,比如 100G,其他 GC 可能一次 Major 或 Full GC 要幾十秒以上,但是對(duì)于 ZGC 不需要那么大暫停。這種細(xì)粒度的優(yōu)化帶來(lái)的副作用就是,把很多環(huán)節(jié)其他 GC 里的 STW 整體處理,拆碎了,放到了更大時(shí)間范圍內(nèi)里去跟業(yè)務(wù)線程并發(fā)執(zhí)行,甚至?xí)苯幼寴I(yè)務(wù)線程幫忙做一些 GC 的操作,從而降低了業(yè)務(wù)線程的處理能力。
總結(jié)
綜上,其實(shí) ZGC 并不是一個(gè)憑空冒出的全新垃圾回收,它結(jié)合前幾代 GC 的思想,同時(shí)在戰(zhàn)術(shù)上做了優(yōu)化以達(dá)到極限的 STW,ZGC 的優(yōu)秀表現(xiàn)有可能會(huì)改變未來(lái)程序編寫方式,站在垃圾收集器的角度,垃圾收集器特別喜歡不可變對(duì)象,原有編程方式鑒于內(nèi)存、GC 能力所限使用可變對(duì)象來(lái)復(fù)用對(duì)象而不是銷毀重建,試想如果有了 ZGC 的強(qiáng)大回收能力的加持,是不是我們就可以無(wú)腦的使用不可變對(duì)象進(jìn)行代碼編寫
審核編輯:劉清
-
計(jì)數(shù)器
+關(guān)注
關(guān)注
32文章
2261瀏覽量
94986 -
cms
+關(guān)注
關(guān)注
0文章
60瀏覽量
10999 -
JVM
+關(guān)注
關(guān)注
0文章
158瀏覽量
12261 -
虛擬機(jī)
+關(guān)注
關(guān)注
1文章
940瀏覽量
28428 -
收集器
+關(guān)注
關(guān)注
0文章
30瀏覽量
3189
原文標(biāo)題:從歷代GC算法角度刨析ZGC
文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
jvm的類加載器的整體結(jié)構(gòu)及過(guò)程解析
![<b class='flag-5'>jvm</b>的類加載器的整體結(jié)構(gòu)及過(guò)程解析](https://file.elecfans.com/web1/M00/C8/88/o4YBAF9wQzuASvvjAAAYyn9-zNc508.png)
Jvm的整體結(jié)構(gòu)和特點(diǎn)
Jvm垃圾回收機(jī)制及性能調(diào)優(yōu)實(shí)戰(zhàn)
帶顏色的JVM垃圾回收三色標(biāo)記法
詳解JVM的垃圾回收算法和垃圾回收器
![詳解<b class='flag-5'>JVM</b>的<b class='flag-5'>垃圾</b>回收算法和<b class='flag-5'>垃圾</b>回收器](https://file.elecfans.com//web2/M00/3A/34/pYYBAGJCn2GAVUPYAAHiqtKrmaU666.png)
JVM內(nèi)存布局的多方面了解
JVM入門之垃圾回收算法
![<b class='flag-5'>JVM</b>入門之<b class='flag-5'>垃圾</b>回收算法](https://file.elecfans.com/web2/M00/90/42/pYYBAGPlvBOAVsKqAAaDbMbMLCw729.png)
JVM內(nèi)存布局詳解
![<b class='flag-5'>JVM</b>內(nèi)存布局詳解](https://file1.elecfans.com/web2/M00/82/48/wKgaomRIiGGAUoDIAAAZjDZYjw0708.png)
詳細(xì)解析JVM中的垃圾回收機(jī)制
垃圾收集器的JVM參數(shù)配置
![<b class='flag-5'>垃圾</b>收集器的<b class='flag-5'>JVM</b>參數(shù)配置](https://file1.elecfans.com/web2/M00/A7/6D/wKgaomUjux-ANVLUAAChVNUuCZ0773.jpg)
jvm調(diào)優(yōu)參數(shù)
jvm參數(shù)的設(shè)置和jvm調(diào)優(yōu)
jvm配置的mx
weblogic jvm參數(shù)配置
從原理聊JVM(一):染色標(biāo)記和垃圾回收算法
![從原理聊<b class='flag-5'>JVM</b>(一):染色標(biāo)記和<b class='flag-5'>垃圾</b>回收算法](https://file1.elecfans.com//web2/M00/04/71/wKgaombERMKAIkVjAAHROjCUdGM843.png)
評(píng)論