前言
上篇文章已經(jīng)為大家詳細(xì)介紹了 JVM 的垃圾收集機制责语,那么這次就一起來看看這些機制究竟是怎樣應(yīng)用到具體的垃圾收集器上的吧。Java 語言和 JVM 在不斷迭代發(fā)展的同時目派,垃圾收集器也在不斷地進化坤候,從最初的的單線程收集器 Serial,到后來的并行收集器 Parallel 和并發(fā)收集器 CMS企蹭、G1白筹,再到垃圾收集器最前沿成果——超低延遲的 Shenandoah 和 ZGC智末,還有不做垃圾收集的垃圾收集器 Epsilon (是的你沒有看錯),正是有了這些垃圾收集器的存在徒河,Java 開發(fā)者才得以從繁瑣的手動管理中解放出來系馆。下面將為大家一一介紹這些垃圾收集器,全文采用“總-分”結(jié)構(gòu)虚青,先總體認(rèn)識一下所有的垃圾收集器它呀,在逐個進行介紹。
一棒厘、垃圾收集器匯總
下圖就是 HotSpot 虛擬機上的已商用的垃圾收集器的關(guān)系圖 (此圖并不包含 Shenandoah 和 ZGC纵穿,因為這兩者目前都還處于實驗階段,且沒有遵循經(jīng)典的分代收集理論奢人,另外的 Epsilon 也不是常規(guī)的垃圾收集器谓媒,因此也沒出現(xiàn)在此圖上)。
圖中的連線表示兩個垃圾收集器之間可以搭配使用何乎,請注意句惯,JDK 9 已不再支持 Serial + CMS 和 ParNew + Serial Old 的搭配組合。如果覺得數(shù)量太多不好記的話支救,可以把上圖中的五個垃圾收集器分為以下三大類:
Serial 類:新生代版本為 Serial抢野,老年代版本為 Serial Old,這兩個都是單線程垃圾收集器各墨。另外指孤,ParNew 相比 Serial 只是增加了多線程并行收集的功能,并無其他太大差別贬堵。
Parallel 類:包括 Parallel Scavenge 和 Parallel Old恃轩,多線程并行垃圾收集器經(jīng)典組合,這個組合更注重于提高程序的吞吐量黎做。
并發(fā)收集器:CMS 和 G1都可以并發(fā)進行垃圾收集叉跛,其中 CMS 只適用于老年代,而 G1 則橫跨新生代和老年代蒸殿。
并發(fā) (concurrent)與并行 (parallel):這里所說的并發(fā)與并行的概念和操作系統(tǒng)里的概念有所不同筷厘,這里的并發(fā)是指垃圾收集線程和用戶線程可以同時執(zhí)行,而并行是指多個垃圾收集線程同時執(zhí)行宏所,但用戶線程必須暫停酥艳。
除了上圖這些經(jīng)典的垃圾收集器,還有一些目前尚處于試驗階段的黑科技收集器楣铁,這部分僅做了解即可玖雁,萬一面試的時候扯到了更扁,還能順帶裝一波逼盖腕。OracleJDK 11 新加入了 ZGC 收集器(目前還處于實驗階段)赫冬,OpenJDK 12 中也加入了 其獨有的 Shenandoah 收集器 (也處于實驗階段),OracleJDK 和 OpenJDK 的區(qū)別這里就不細(xì)說了溃列。這兩款垃圾收集器都以超低延遲為賣點劲厌,也就是盡量縮短垃圾收集時用戶線程的暫停 (Stop The World)的時間,這兩款收集器都宣稱可以把垃圾收集的停頓時間控制在 10 毫秒以內(nèi)听隐,比之前最牛X的G1的延遲還要短补鼻。最后還有適用于微服務(wù)領(lǐng)域的 Epsilon,下面就為大家一一介紹這些琳瑯滿目雅任、五花八門的垃圾收集器风范。
二、垃圾收集器詳解
2.1 新生代收集器
2.1.1 Serial 收集器
Serial 收集器是最基礎(chǔ)沪么、歷史最悠久的垃圾收集器硼婿,在 JDK 1.3.1 之前是 HotSpot 虛擬機新生代收集器的唯一選擇。既然如此禽车,也不能指望它有多么強大的功能了寇漫,這是一款單線程收集器,不僅只有一個垃圾收集線程殉摔,更難受的是它在進行垃圾收集時必須暫停所有用戶線程州胳,也就是說垃圾收集時需要全程 “Stop The World”,如圖:
可見 Serial 在進行垃圾收集是必須“Stop The World”逸月,而且其單線程的收集效率并不高栓撞,可能造成用戶程序的長時間停頓。上篇文章已經(jīng)給大家介紹過了新生代和老年代的概念彻采,接下來補充一下圖中安全點的概念:
安全點 (safepoint):安全點是代碼指令中特定的位置腐缤,這些位置記錄著棧和寄存器里那些位置是引用,這樣收集器在掃描垃圾對象時就不需要一個不漏地從方法區(qū)等 GC Roots 開始查找肛响。安全點位置一般選在方法調(diào)用岭粤、循環(huán)跳轉(zhuǎn)和異常跳轉(zhuǎn)的代碼指令處,因為這些位置的代碼可以“長時間運行”特笋。
2.1.2 ParNew 收集器
ParNew 收集器實質(zhì)上就是 Serial 收集器的多線程版本剃浇,這也是它的唯一優(yōu)勢,除了同時使用多線程進行垃圾收集之外猎物,其他的行為包括 Serial 所有可用的控制參數(shù) (比如 -XX:SurvivorRatio虎囚,-XX:PretenureSizeThreshold,-XX:HandlePromotionFailure等)蔫磨,還垃圾收集算法淘讥、Stop The World、對象分配規(guī)則堤如、回收策略等都與 Serial 收集器完全一致蒲列。這兩個收集器的底層代碼大部分也是相通的窒朋。
可以使用 -XX:+/-UseParNewGC選項來強制指定或禁用 ParNew 收集器,ParNew 還有一個特點蝗岖,就是在使用 -XX:UseConcMarkSweepGC 參數(shù)激活 CMS 收集器后侥猩,新生代會默認(rèn)使用 ParNew 收集器。
2.1.3 Parallel Scavenge 收集器
Parallel Scavenge 收集器也是一款作用于新生代抵赢、基于標(biāo)記-復(fù)制算法的多線程并行垃圾收集器欺劳,與 ParNew 有很多相似之處。相比 CMS铅鲤、G1划提、Shenandoah 和ZGC 這些致力于降低停頓時間,也就是低延遲的收集器邢享,Parallel Scavenge 是吞吐量 (throughput)優(yōu)先的收集器腔剂,吞吐量是指 CPU 用于運行用戶程序的時間與 CPU 總消耗時間的比值:
低延遲和高吞吐量的收集器有著不同的適用場景,前者適用于與用戶交互較多或需要保證服務(wù)器響應(yīng)質(zhì)量的場景驼仪,低延遲可以帶來良好的用戶體驗掸犬,而高吞吐量可以讓 CPU 把更多的時間用在運行用戶程序上面,可以更快完成任務(wù)绪爸,適用于交互性不強的后臺運算場景湾碎。Parallel Scavenge 提供了兩個參數(shù)用于精確控制吞吐量,分別是控制最大垃圾收集停頓時間的 -XX:MaxGCPauseMillis參數(shù)和直接設(shè)置吞吐量大小的 -XX:GCTimeRatio奠货。
Parallel Scavenge 還有一個比較特色的開關(guān)參數(shù):-XX:+UseAdaptiveSizePolicy介褥,激活這個參數(shù)后,會開啟自適應(yīng)策略递惋,也就是無需我們手動設(shè)置新生代大小 (-Xmn)柔滔、Eden 與 Survivor 的比例 (-XX:SurvivorRatio)和直接晉升老年代對象大小(-XX:PretenureSizeThreshold)萍虽,虛擬機會根據(jù)系統(tǒng)運行狀態(tài)并收集性能監(jiān)控信息睛廊,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時間或最大的吞吐量。
2.2 老年代收集器
2.2.1 Serial Old 收集器
這就是 Serial 的老年代版本杉编,也是單線程收集器超全,使用標(biāo)記-整理算法,是 Serial 的黃金搭檔:
這個搭檔主要用在客戶端模式下邓馒,除此之外嘶朱,Serial Old 還有兩個用途,那就是和 JDK 5 及之前的 Parallel Scavenge搭配使用光酣,以及作為 CMS 收集器失敗之后的備胎疏遏。
2.2.2 Parallel Old 收集器
這個是 Parallel Scavenge 的老年代版本,但是直到 JDK 6 才正式提供,之前的 Parallel Scavenge 只能和單線程的 Serial Old 搭配使用财异,完全發(fā)揮不了其優(yōu)勢下翎,Parallel Old 出現(xiàn)后,“吞吐量優(yōu)先”收集器終于也有了黃金搭檔:
2.2.3 CMS 收集器
CMS (Concurrent Mark Sweep) 收集器是一款致力于獲取最短停頓時間的收集器宝当,從它的名字中可以看出這款收集器有兩個重要特點:一,這是一款可以并發(fā)進行垃圾收集的收集器胆萧;二庆揩,這款收集器是基于標(biāo)記清除-算法的。它的運作過程相對于之前不能并發(fā)的垃圾收集器更加復(fù)雜跌穗,大體分為以下四個步驟:
-
初始標(biāo)記 (CMS initial mark)
這一步僅僅是標(biāo)記一下與 GC Roots 直接關(guān)聯(lián)的對象订晌,雖然不是并發(fā)執(zhí)行,但是速度很快蚌吸,用戶程序會有短暫的暫停锈拨。
-
并發(fā)標(biāo)記 (CMS concurrent mark)
這一步比較耗時,需要遍歷所有與 GC Roots 有關(guān)聯(lián)的對象羹唠,但是可以與用戶線程并發(fā)執(zhí)行奕枢,所以對用戶程序影響不大。
-
重新標(biāo)記 (CMS remark)
由于并發(fā)標(biāo)記過程中用戶程序是不暫停的佩微,所以有可能引起原來的標(biāo)記對象產(chǎn)生變動缝彬,而重新標(biāo)記的作用就是修正那些變動的標(biāo)記記錄,這一階段雖然無法并發(fā)執(zhí)行哺眯,但是工作量很小谷浅,所以持續(xù)時間也很短。
-
并發(fā)清除 (CMS concurrent sweep)
這一階段就是清除掉可回收的對象奶卓,回想上篇文章介紹的標(biāo)記-清除算法一疯,在清除掉垃圾對象后并不需要移動存活對象,所以這一階段可以與用戶線程并發(fā)執(zhí)行夺姑。
綜上墩邀,CMS 收集器在運行過程中只需在初始標(biāo)記階段和重新標(biāo)記階段暫停用戶程序,而且時間很短盏浙,其他階段均可與用戶程序并發(fā)執(zhí)行磕蒲,這就是它實現(xiàn)超短停頓的秘密所在。
CMS 的優(yōu)勢很明顯只盹,就是并發(fā)收集和低停頓辣往,但也不是完美無缺的,它主要有以下三個明顯缺點:
CMS 收集器對處理器資源殖卑,也就是 CPU 核心數(shù)非常敏感站削,這也是所有并發(fā)設(shè)計的程序的共同特點。雖然它并發(fā)特點帶來了低停頓的優(yōu)勢孵稽,但是由于擠占了處理器資源许起,導(dǎo)致總吞吐量降低十偶,程序運行總時間也會相應(yīng)延長。
CMS 無法處理“浮動垃圾”园细,因為并發(fā)標(biāo)記階段和并發(fā)清除階段用戶程序是在繼續(xù)執(zhí)行的惦积,自然會繼續(xù)產(chǎn)生垃圾對象,但是這些垃圾對象產(chǎn)生在標(biāo)記階段之后猛频,所以無法被標(biāo)記出來狮崩,自然也就無法被清除,而這可能會引發(fā)停頓時間較長的 Full GC鹿寻。
空間碎片睦柴,既然 CMS 是基于標(biāo)記-清除算法的,也就不能避免產(chǎn)生空間碎片了毡熏,空間碎片就是不連續(xù)的可用內(nèi)存坦敌,這可能導(dǎo)致明明有剩余空間,但就是放不下新對象痢法,從而提前觸發(fā) Full GC狱窘。
2.3 G1 收集器
Garbage First 收集器,簡稱 G1财搁,可以說是垃圾收集器技術(shù)史上里程碑式的成果训柴,它開創(chuàng)了收集器面向局部收集的設(shè)計思路和基于 Region 的內(nèi)存布局結(jié)構(gòu)。也是從 G1 開始妇拯,垃圾收集器幻馁,包括后來的 Shenandoah 和 ZGC 都不再局限于只回收新生代或只回收老年代,而是面向整個 Java 堆越锈。
G1 收集器最大的特色就是可預(yù)測的停頓仗嗦,用戶可以通過 -XX:MaxPauseMillis 參數(shù) (默認(rèn)200毫秒)指定期望的最大停頓時間,但不能隨意指定甘凭,要切合實際稀拐,然后 G1 會根據(jù)這一目標(biāo)值篩選并回收那些回收價值最高的可回收對象,那么 G1 是怎樣做到這一點的呢丹弱?關(guān)鍵就在于 G1 基于 Region 的內(nèi)存布局德撬,先來看一下 G1 和之前垃圾收集器的堆內(nèi)存布局對比:
由此可見,雖然 G1 仍然遵循分帶收集理論躲胳,但是內(nèi)存區(qū)域不再按照固定大小的新生代和老年代進行劃分蜓洪,而是把連續(xù)的 Java 對劃分成多個大小相等的獨立區(qū)域 (Region),每一個 Region 都可以根據(jù)需要扮演新生代的 Eden 空間坯苹、Survivor 空間或老年代空間隆檀。收集器可以對扮演不同角色的 Region 采用不同的策略進行處理,這樣無論是新創(chuàng)建的對象還是已經(jīng)存活了一段時間的對象,抑或是熬過了多次收集的就對象都能獲得很好的收集效果恐仑。Region 中還有一類特殊的 Humongous 區(qū)域泉坐,專門用來存放大對象,G1 認(rèn)為只要大小超過一個 Region 容量一半的對象就可判定為大對象裳仆,每個 Region 的大小可以通過參數(shù) -XX:G1HeapRedionSize 設(shè)定腕让,取值范圍為 1MB~32MB,且為 2 的 N 次冪歧斟,對于那些大小超過整個 Region 大小的超大對象纯丸,將會被存放在 N 個連續(xù)的 Humongous Region 中,G1 一般會把 Humongous Region 看做老年代构捡。
在把內(nèi)存分成 Region 管理之后,G1 就可以對這些 Region 各個擊破了壳猜,其停頓時間之所以可控勾徽,是因為 G1 在垃圾收集時并不會把整個 Java 堆當(dāng)做回收區(qū)域,而是只收集那些回收價值最高的 Region统扳,保證能在指定最大停頓時間內(nèi)回收完畢喘帚,回收價值是指回收所獲得的空間大小及耗費時間的權(quán)衡結(jié)果。這樣就保證了 G1 能在指定時間內(nèi)獲得盡可能高的回收效率咒钟。
G1 的回收過程大致可以分為以下四個步驟:
初始標(biāo)記 (initial marking):僅僅標(biāo)記直接與 GC Roots 關(guān)聯(lián)的對象吹由,停頓時間很短;
并發(fā)標(biāo)記 (Concurrent Marking):從 GC Roots 開始對堆中對象進行可達(dá)性分析朱嘴,耗時較長倾鲫,但能與用戶程序并發(fā)執(zhí)行,所以不會停頓萍嬉;
最終標(biāo)記 (Final Marking):用戶程序并發(fā)執(zhí)行導(dǎo)致對象引用關(guān)系變化乌昔,修正變化的引用,此階段會短暫地暫停用戶程序壤追;
篩選回收 (Live Data Counting and Evacuation):更新 Region 的統(tǒng)計數(shù)據(jù)磕道,對各個 Region 的回收價值和成本進行排序,并根據(jù)用戶所期望的停頓時間指定回收計劃行冰,可以自由選擇任意多個 Region 構(gòu)成回收集溺蕉,然后把需要回收的 Region 中存貨對象復(fù)制到空的 Region 中,在清理掉舊 Region 的全部空間悼做,這里涉及存活對象的移動疯特,必須暫停用戶線程,是由多條收集器線程并行完成的肛走。
G1 和 CMS 都是以低停頓為目標(biāo)的收集器辙芍,所以經(jīng)常被拿來比較孰優(yōu)孰劣,雖然 G1 相比 CMS 優(yōu)勢明顯,但也并非全方位的碾壓故硅,G1相比 CMS 的優(yōu)缺點如下:
-
G1 優(yōu)點:
可以指定最大停頓時間庶灿;
分 Region 管理內(nèi)存,按受益動態(tài)確定回收區(qū)域吃衅;
不會產(chǎn)生內(nèi)存碎片:G1 的內(nèi)存布局并不是固定大小以及固定數(shù)量的分代區(qū)域劃分往踢,而是把連續(xù)的Java堆劃分為多個大小相等的獨立區(qū)域 (Region),G1 從整體來看是基于“標(biāo)記-整理”算法實現(xiàn)的收集器徘层,但從局部 (兩個Region 之間)上看又是基于“標(biāo)記-復(fù)制”算法實現(xiàn)峻呕,不會像 CMS (“標(biāo)記-清除”算法) 那樣產(chǎn)生內(nèi)存碎片。
-
G1 缺點:
G1 需要記憶集 (具體來說是卡表)來記錄新生代和老年代之間的引用關(guān)系趣效,這種數(shù)據(jù)結(jié)構(gòu)在 G1 中需要占用大量的內(nèi)存瘦癌,可能達(dá)到整個堆內(nèi)存容量的 20% 甚至更多。而且 G1 中維護記憶集的成本較高跷敬,帶來了更高的執(zhí)行負(fù)載讯私,影響效率。
按照《深入理解Java虛擬機》作者的說法西傀,CMS 在小內(nèi)存應(yīng)用上的表現(xiàn)要優(yōu)于 G1斤寇,而大內(nèi)存應(yīng)用上 G1 更有優(yōu)勢,大小內(nèi)存的分水嶺是6GB到8GB拥褂。
2.4 最前沿科技成果:低延遲垃圾收集器
之前最先進的 G1 收集器早在 JDK 7 上就已經(jīng)發(fā)布了成熟版娘锁,而截至目前的2020年初,JDK 版本已經(jīng)來到了 JDK 13饺鹃,與此同時莫秆,垃圾收集器領(lǐng)域也早已有了更先進的黑科技,其中的代表者就是號稱可以將停頓時間控制在10毫秒內(nèi)低延遲收集器——Shenandoah 和 ZGC悔详,它們最牛X的地方在于并發(fā)程度更高馏锡,連移動存活對象 (也就是標(biāo)記-整理算法的整理階段)都可以做到并發(fā)執(zhí)行 (不過二者的實現(xiàn)原理有所區(qū)別):
由圖可知杯道,相比之前的收集器,Shenandoah 和 ZGC 在工作過程中幾乎全程并發(fā)责蝠,只有在初始標(biāo)記党巾、最終標(biāo)記這些階段有短暫的暫停,而且這些停頓時間與堆容量和堆中對象數(shù)量沒有正比例關(guān)系霜医,這才可以將停頓時間控制在驚人的10毫秒以內(nèi)齿拂。
2.4.1 Shenandoah 收集器
Shenandoah 是由 ReadHat 公司獨立發(fā)展的新型垃圾收集器,并在2014年貢獻(xiàn)給了 OpenJDK肴敛,并成為 OpenJDK 12 的正式特性之一署海,但是以 Oracle 公司的尿性吗购,卻不愿把它添加到 OracleJDK 中,這也導(dǎo)致了免費開源的 OpenJDK 反而比商業(yè)收費的 OracleJDK 功能更多砸狞,實屬罕見捻勉。
Shenandoah 與 G1 有很多相似之處,比如都是基于 Region 的內(nèi)存布局刀森,都有用于存放大對象的 Humongous Region踱启,默認(rèn)回收策略也是優(yōu)先處理回收價值最大的 Region。不過也有三個重大的區(qū)別:
最最重要的區(qū)別研底,Shenandoah 支持并發(fā)的整理算法埠偿,G1 的整理階段雖是多線程并行,但無法與用戶程序并發(fā)執(zhí)行榜晦;
默認(rèn)不使用分代收集理論冠蒋;
使用連接矩陣 (Connection Matrix)記錄跨 Region 的引用關(guān)系,替換掉了 G1 中的記憶級 (Remembered Set)乾胶,內(nèi)存和計算成本更低抖剿。
Shenandoah 收集器的工作原理相比 G1 要復(fù)雜不少,其運行流程示意圖如下:
可見 Shenandoah 的并發(fā)程度明顯比 G1 更高胚吁,只需要在初始標(biāo)記牙躺、最終標(biāo)記愁憔、初始引用更新和最終引用更新這幾個階段進行短暫的“Stop The World”腕扶,其他階段皆可與用戶程序并發(fā)執(zhí)行,其中最重要的并發(fā)標(biāo)記吨掌、并發(fā)回收和并發(fā)引用更新詳情如下:
-
并發(fā)標(biāo)記( Concurrent Marking)
與G1一樣半抱,遍歷對象圖,標(biāo)記出全部可達(dá)的對象膜宋,這個階段是與用戶線程一起并發(fā)的窿侈,時間長短取決于堆中存活對象的數(shù)量以及對象圖的結(jié)構(gòu)復(fù)雜程度。
-
并發(fā)回收( Concurrent Evacuation)
并發(fā)回收階段是 Shenandoah 與之前 HotSpot 中其他收集器的核心差異。在這個階段, Shenandoah 要把待回收 Region 里面的存活對象先復(fù)制一份到其他未被使用的 Region之中椭豫。復(fù)制對象這件事情如果將用戶線程凍結(jié)起來再做那是相當(dāng)簡單的摆寄,但如果兩者必須要同時并發(fā)進行的話,就變得復(fù)雜起來了骇陈。其困難點是在移動對象的同時,用戶線程仍然可能不停對被移動的對象進行讀寫訪問,移動對象是一次性的行為殉农,但移動之后整個內(nèi)存中所有指向該對象的引用都還是舊對象的地址,這是很難一瞬間全部改變過來的局荚。對于并發(fā)回收階段遇到的這些困難超凳, Shenandoah 將會通過讀屏障和被稱為“ Brooks Pointers”的轉(zhuǎn)發(fā)指針來解決愈污。并發(fā)回收階段運行的時間長短取決于回收集的大小。
Brooks Pointers 簡要介紹:這是一種轉(zhuǎn)發(fā)指針 (Forwarding Pointer)轮傍,原理就是在所有的對象上新添加一個指針暂雹,初始狀態(tài)下該指針指向?qū)ο蟊旧恚诶厥者^程中金麸,如果該對象是存活對象擎析,則需要將其從回收區(qū)域移動到目標(biāo)區(qū)域 (其實就是在目標(biāo)區(qū)域復(fù)制一個新對象,這就是標(biāo)記-整理算法的整理階段挥下,之前的 G1 收集器在此階段無法與用戶程序并發(fā)執(zhí)行)揍魂,然后把舊對象的轉(zhuǎn)發(fā)指針指向新的對象,這樣用戶程序在并發(fā)執(zhí)行的情況下棚瘟,就不會訪問到舊對象了现斋。
-
并發(fā)引用更新( Concurrent Update Reference)
這個階段是與用戶線程一起并發(fā)的,時間長短取決于內(nèi)存中涉及的引用數(shù)量的多少偎蘸。并發(fā)引用更新與并發(fā)標(biāo)記不同庄蹋,它不再需要沿著對象圖來搜索,只需要按照內(nèi)存物理地址的順序迷雪,線性地搜索出引用類型限书,把舊值改為新值即可。
Shenandoah 的高并發(fā)度讓它實現(xiàn)了超低的停頓時間章咧,但是更高的復(fù)雜度也伴隨著更高的系統(tǒng)開銷倦西,這在一定程度上會影響吞吐量,下圖是 Shenandoah 與之前各種收集器在停頓時間維度和系統(tǒng)開銷維度上的對比:
OracleJDK 并不支持 Shenandoah赁严,如果你用的是 OpenJDK 12 或某些支持 Shenandoah 移植版的 JDK 的話扰柠,可以通過以下參數(shù)開啟 Shenandoah:
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
2.4.2 ZGC 收集器
Z Garbage Collector,簡稱 ZGC疼约,是 JDK 11 中新加入的尚在實驗階段的低延遲垃圾收集器卤档。它和 Shenandoah 同屬于超低延遲的垃圾收集器,但在吞吐量上比 Shenandoah 有更優(yōu)秀的表現(xiàn)程剥,甚至超過了 G1劝枣,接近了“吞吐量優(yōu)先”的 Parallel 收集器組合,可以說近乎實現(xiàn)了“魚與熊掌兼得”织鲸。
ZGC 的內(nèi)存布局
與 Shenandoah 和 G1 一樣舔腾,ZGC 也采用基于 Region 的堆內(nèi)存布局,但與它們不同的是昙沦, ZGC 的 Region 具有動態(tài)性琢唾,也就是可以動態(tài)創(chuàng)建和銷毀,容量大小也是動態(tài)的盾饮,有大采桃、中懒熙、小三類容量:
小型 Region (Small Region):容量固定為 2MB,用于放置小于 256KB 的小對象普办。
中型 Region (M edium Region):容量固定為 32MB工扎,用于放置大于等于 256KB 但小于 4MB 的對 象。
大型 Region (Large Region):容量不固定衔蹲,可以動態(tài)變化肢娘,但必須為 2MB 的整數(shù)倍,用于放置 4MB 或以上的大對象舆驶。每個大型 Region 中只會存放一個大對象橱健,這也預(yù)示著雖然名字叫作“大型 Region”,但它的實際容量完全有可能小于中型 Region沙廉,最小容量可低至 4MB拘荡。
與 Shenandoah 一樣,ZGC 在工作過程中也幾乎是全程與用戶程序并發(fā)的撬陵,重點也是實現(xiàn)了標(biāo)記-整理算法的整理階段可以與用戶程序并發(fā)執(zhí)行珊皿。但是二者的實現(xiàn)方式不同,Shenandoah 是在對象身上添加轉(zhuǎn)發(fā)指針的方法巨税,而 ZGC 則是直接在指針上動手腳蟋定,也就是傳說中的染色指針 (Colored Pointers),這個指針就是 Java 對象的引用草添,例如:
Object o = new Object();
其中“o” 只是一個引用驶兜,也就是指針,指向存在堆上的對象實例果元,引用自身也是要占內(nèi)存的促王,普通引用在32位機器占4個字節(jié)犀盟,在64位機器上而晒,開啟壓縮指針 (-XX:+UseCompressedOops) 的話占4個字節(jié),不開啟的話占8個字節(jié)阅畴。ZGC 的染色指針結(jié)構(gòu)如下 (不支持32位機器和壓縮指針):
得益于染色指針上標(biāo)志位的支持倡怎,ZGC 也可以像 Shenandoah 那樣,實現(xiàn)了在移動存活對象的過程中可以與用戶程序并發(fā)執(zhí)行贱枣,且效率更高监署。ZGC 還用到了很多其他的黑科技,原理過于復(fù)雜纽哥,就不在這里詳述了钠乏。
在 JDK 11 及以上版本,可以通過以下參數(shù)開啟 ZGC:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
2.5 最奇葩的垃圾收集器——Epsilon
上面介紹的各種收集器春塌,比如 G1晓避、Shenandoah 和 ZGC 等都是越來越復(fù)雜簇捍,越來越先進, 而 JDK 11 新加入的 Epsilon 卻是反其道而行俏拱,這款收集器不會做任何垃圾收集的操作暑塑,也許叫做“內(nèi)存分配器”更加合適。雖然很奇葩锅必,但是它還是有用武之地的事格,比如越來越火的微服務(wù)領(lǐng)域,如果系統(tǒng)運行時間很短搞隐,在堆內(nèi)存耗盡之前就可以結(jié)束驹愚,那么垃圾收集也就沒有任何意義了,這正是 Epsilon 的使用場景劣纲。
總結(jié)
本文為大家介紹了目前 HotSpot 虛擬機上的所有垃圾收集器么鹤,有的已經(jīng)久經(jīng)沙場,有的仍處于試驗階段味廊,但有望在未來成為主流蒸甜,在實際應(yīng)用中,大家可以根據(jù)具體場景選擇合適的垃圾收集器余佛。
參考資料: