日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

java垃圾收集器

發(fā)布時(shí)間:2023/12/20 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java垃圾收集器 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

簡(jiǎn)介

Serial 收集器

ParNew 收集器

并行(Parallel)

并發(fā)(Concurrent)

Parallel Scavenge 收集器

Serial Old 收集器

Parallel Old 收集器

CMS收集器

G1收集器

G1簡(jiǎn)介

G1細(xì)節(jié)

G1 Minor GC流程

G1 Mixed GC流程

G1特點(diǎn)

G1與CMS的區(qū)別與選擇

ZGC收集器

垃圾處理器總結(jié)


簡(jiǎn)介

如果說收集算法是內(nèi)存回收的方法論,垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。Java 虛擬機(jī)規(guī)范中對(duì)垃圾收集器應(yīng)該如何實(shí)現(xiàn)并沒有任何規(guī)定,因此不同的廠商、不同版本 的虛擬機(jī)所提供的垃圾收集器都可能會(huì)有很大的差別,并且一般都會(huì)提供參數(shù)供用戶根 據(jù)自己的應(yīng)用特點(diǎn)和要求組合出各個(gè)年代所使用的收集器。這里討論的收集器基于Sun HotSpot虛擬機(jī)1.6Update 22,這個(gè)虛擬機(jī)包含的所有收集器,如圖

注意:這個(gè)關(guān)系不是一成不變的,由于維護(hù)和兼容性測(cè)試的成本,在JDK 8時(shí)將Serial+CMS- ParNew+Serial Old這兩個(gè)組合聲明為廢棄(JEP 173) ,并在JDK 9中完全取消了這些組合的支持(JEP214)。

展示了 7種作用于不同分代的收集器包括JDK 1.6_Updatel4后引入的 Early AccessG1收集器),如果兩個(gè)收集器之間存在連線就說明它們可以搭配使用。

在介紹這些收集器各自的特性之前,我們先來明確一個(gè)觀點(diǎn):雖然我們是在對(duì)各個(gè) 收集器進(jìn)行比較,但并非為了挑選一個(gè)最好的收集器出來。因?yàn)橹钡浆F(xiàn)在為止還沒有最 好的收集器出現(xiàn),更加沒有萬(wàn)能的收集器,所以我們選擇的只是對(duì)具體應(yīng)用場(chǎng)景最合適的收 集器。這點(diǎn)不需要多加解釋就能證明:如果有一種放之四海皆準(zhǔn)、任何場(chǎng)景下都適用的 完美收集器存在,那HotSpot虛擬機(jī)就沒必要實(shí)現(xiàn)那么多不同的收集器了。

Serial 收集器

Serial收集器是最基本、歷史最悠久的收集器,曾經(jīng)(在JDK 1.3.1之前)是虛擬機(jī) 新生代收集的唯一選擇。大家看名字就知道,這個(gè)收集器是一個(gè)單線程的收集器,但它的“單線程”的意義并不僅僅是說明它只會(huì)使用一個(gè)CPU或一條收集線程去完成垃圾收 集工作,更重要的是在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程Sim將這件 事情稱之為“Stop The World”)直到它收集結(jié)束“Stop The World”這個(gè)名字也許聽 起來很酷,但這項(xiàng)工作實(shí)際上是由虛擬機(jī)在后臺(tái)自動(dòng)發(fā)起和自動(dòng)完成的,在用戶不可見 的情況下把用戶的正常工作的線程全部停掉,這對(duì)很多應(yīng)用來說都是難以接受的。你想 想,要是你的電腦每運(yùn)行一個(gè)小時(shí)就會(huì)暫停響應(yīng)5分鐘,你會(huì)有什么樣的心情?圖3?6 示意了 Serial / Serial Old收集器的運(yùn)行過程。

對(duì)于“Stop The World”帶給用戶的惡劣體驗(yàn),虛擬機(jī)的設(shè)計(jì)者們表示完全理解,但 也表示非常委屈「'你媽媽在給你打掃房間的時(shí)候,肯定也會(huì)讓你老老實(shí)實(shí)地在椅子上 或房間外待著,如果她一邊打掃,你一邊亂扔紙屑,這房間還能打掃完嗎? ”這確實(shí)是 一個(gè)合情合理的矛盾,雖然垃圾收集這項(xiàng)工作聽起來和打掃房間屬于一個(gè)性質(zhì)的,但實(shí) 際上肯定還要比打掃房間復(fù)雜得多啊!

新生代采用標(biāo)記-復(fù)制算法,老年代采用標(biāo)記-整理算法。

JDK1.3, HotSpot虛擬機(jī)開發(fā)團(tuán)隊(duì) 為消除或減少工作線程因內(nèi)存回收而導(dǎo)致停頓的努力一直在進(jìn)行著,從Serial收集器 到Parallel收集器,再到Concurrent Mark Sweep (CMS)現(xiàn)在還未正式發(fā)布的Garbage First (G1)收集器,我們看到了一個(gè)個(gè)越來越優(yōu)秀(也越來越復(fù)雜)的收集器的出現(xiàn), 用戶線程的停頓時(shí)間在不斷縮短,但是仍然沒有辦法完全消除(這里暫不包括RTSJ中 的收集器)。尋找更優(yōu)秀的垃圾收集器的工作仍在繼續(xù)!

寫到這里,筆者似乎已經(jīng)把Serial收集器描述成一個(gè)老而無(wú)用,食之無(wú)味棄之可惜 的雞肋了,但實(shí)際上到現(xiàn)在為止,它依然是虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收 集器。它也有著優(yōu)于其他收集器的地方:簡(jiǎn)單而高效(與其他收集器的單線程比),對(duì) 于限定單個(gè)CPU的環(huán)境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收 集自然可以獲得最高的單線程收集效率。在用戶的桌面應(yīng)用場(chǎng)景中,分配給虛擬機(jī)管理 的內(nèi)存一般來說不會(huì)很大,收集幾十兆甚至一兩百兆的新生代(僅僅是新生代使用的 內(nèi)存,桌面應(yīng)用基本上不會(huì)再大了),停頓時(shí)間完全可以控制在幾十毫秒最多一百多毫 秒以內(nèi),只要不是頻繁發(fā)生,這點(diǎn)停頓是可以接受的。所以,Serial收集器對(duì)于運(yùn)行在 Client模式下的虛擬機(jī)來說是一個(gè)很好的選擇

ParNew 收集器

ParNew收集器其實(shí)就是Serial收集器的多線程版本,除了使用多條線程進(jìn)行垃圾 收集之外,其余行為包括Serial收集器可用的所有控制參數(shù)

(例如:-XX:SurvivorRatio-XX:PretenureSizeThreshold, -XX:HandlePromotionFailure 等)、收集算法、Stop The World.對(duì)象分配規(guī)則、回收策略等都與Serial收集器完全一樣,實(shí)現(xiàn)上這兩種收集器也 共用了相當(dāng)多的代碼。ParNew收集器的工作過程如圖

新生代采用標(biāo)記-復(fù)制算法,老年代采用標(biāo)記-整理算法。

ParNew收集器除了支持多線程并行收集之外,其他與Seril收集器相比并沒有太多創(chuàng)新之處,但它卻是不少運(yùn)行在服務(wù)端模式下的Hotspot虛擬機(jī),尤其是JDK 7之前的遺留系統(tǒng)中首選的新生代收集器,其中有一個(gè)與功能、性能無(wú)關(guān)但其實(shí)很重要的原因是:除了Serial收器外,目前只有它能與CMS收集器配合工作。

在JDK 5發(fā)布時(shí), HotSpot推出了一款在強(qiáng)交互應(yīng)用中幾乎可稱為具有劃時(shí)代意義的垃圾收集器-CMS收集器。這款收集器是HotSpot虛擬機(jī)中第一款真正意義上支持并發(fā)的垃圾收集器,它首次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作。

遺憾的是, CMS作為老年代的收集器,卻無(wú)法與JDK 1.4.0中已經(jīng)存在的新生代收集器ParallelScavenge配合工作[1],所以在JDK5中使用CMS來收集老年代的時(shí)候,新生代只能選擇ParNew或者Serial收集器中的一個(gè)。ParNew收集器是激活CMS后(使用-Xx: +UseConcMarkSweepGC選項(xiàng))的默認(rèn)新生代收集器,也可以使用-Xx: +/-UseParNewGC選項(xiàng)來強(qiáng)制指定或者禁用它。

可以說直到CMS的出現(xiàn)才鞏固了ParNew的地位,但成也蕭何敗也蕭何,隨著垃圾收集器技術(shù)的不斷改進(jìn),更先進(jìn)的G1收集器帶著CMS繼承者和替代者的光環(huán)登場(chǎng)。G1是一個(gè)面向全堆的收集器,不再需要其他新生代收集器的配合工作。所以自JDK 9開始, ParNew加CMS收集器的組合就不再是官方推薦的服務(wù)端模式下的收集器解決方案了。官方希望它能完全被G1所取代,甚至還取消了ParNew加Serial old以及Serial加CMS這兩組收集器組合的支持(其實(shí)原本也很少人這樣使用) ,并直接取消了xx: +UseParNewGC參數(shù),這意味著ParNew和CMS從此只能互相搭配使用,再也沒有其他收集器能夠和它們配合了。

讀者也可以理解為從此以后, ParNew合并入CMS,成為它專門處理新生代的組成部分。ParNew可以說是HotSpot虛擬機(jī)中第一款退出歷史舞臺(tái)的垃圾收集器。

ParNew收集器在單核心處理器的環(huán)境中絕對(duì)不會(huì)有比Serial收集器更好的效果,甚至由于存在線程交互的開銷,該收集器在通過超線程(Hyper-Threading)技術(shù)實(shí)現(xiàn)的偽雙核處理器環(huán)境中都不能百分之百保證超越Serial收集器。當(dāng)然,隨著可以被使用的處理器核心數(shù)量的增加, ParNew對(duì)于垃圾收集時(shí)系統(tǒng)資源的高效利用還是很有好處的。它默認(rèn)開啟的收集線程數(shù)與處理器核心數(shù)量相同,在處理器核心非常多(譬如32個(gè),現(xiàn)在CPU都是多核加超線程設(shè)計(jì),服務(wù)器達(dá)到或超過32個(gè)邏輯核心的情況非常普遍)的環(huán)境中,可以使用-xx: ParallelGCThreads參數(shù)來限制垃圾收集的線程數(shù)。

注意 從ParNew收集器開始,后面還將會(huì)接觸到幾款并發(fā)和并行的收集器。在大家可能 產(chǎn)生疑惑之前,有必要先解釋兩個(gè)名詞:并發(fā)和并行。這兩個(gè)名詞都是并發(fā)編程中的概 念,在談?wù)摾占鞯纳舷挛恼Z(yǔ)境中,他們可以解釋為:

并行Parallel)

指多條垃圾收集線程并行工作,但此時(shí)用戶線程仍然處于等待狀態(tài)。其實(shí)指垃圾收集線程,內(nèi)部多線程工作。?

并發(fā)Concurrent)

指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的,可能 會(huì)交替執(zhí)行),用戶程序繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行于另一個(gè)CPU上。指垃圾收集線程與用戶線程,兩者多線程工作。

Parallel Scavenge 收集器

Parallel Scavenge收集器也是一個(gè)新生代收集器它也是使用復(fù)制算法的收集器又是并行的多線程收集器……看上去和ParNew都一樣那它有什么特別之處呢

-XX:+UseParallelGC使用 Parallel 收集器+ 老年代串行-XX:+UseParallelOldGC使用 Parallel 收集器+ 老年代并行

Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的 關(guān)注點(diǎn)盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目 標(biāo)則是達(dá)到一個(gè)可控制的吞吐量(Throughput)。

所謂吞吐量就是CPU用于運(yùn)行用戶代 碼的時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間/ (運(yùn)行用戶代碼時(shí) 間+垃圾收集時(shí)間),虛擬機(jī)總共運(yùn)行了 100分鐘,其中垃圾收集花掉1分鐘,那吞吐 量就是99%

停頓時(shí)間越短就越適合需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶的體 驗(yàn);而高吞吐量則可以最高效率地利用CPU時(shí)間,盡快地完成程序的運(yùn)算任務(wù),主要適 合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。

Parallel Scavenge收集器提供了兩個(gè)參數(shù)用壬精確控制吞吐量,分別是控制 最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMillis參數(shù)及直接設(shè)置吞吐量大小的 -XX:GCTimeRatio 參數(shù)。

MaxGCPauseMillis參數(shù)允許的值是一個(gè)大于0的毫秒數(shù),收集器將盡力保證內(nèi)存回 收花費(fèi)的時(shí)間不超過設(shè)定值。不過大家不要異想天開地認(rèn)為如果把這個(gè)參數(shù)的值設(shè)置得 稍小一點(diǎn)就能使得系統(tǒng)的垃圾收集速度變得更快,GC停頓時(shí)間縮短是以犧牲吞吐量和 新生代空間來?yè)Q取的:系統(tǒng)把新生代調(diào)小一些,收集300MB新生代肯定比收集500MB 快吧,這也直接導(dǎo)致垃圾收集發(fā)生得更頻繁一些,原來10秒收集一次、每次停頓100 毫秒,現(xiàn)在變成5秒收集一次、每次停頓70毫秒。停頓時(shí)間的確在下降,但吞吐量也 降下來了。

GCTimeRatio參數(shù)的值應(yīng)當(dāng)是一個(gè)大于0小于100的整數(shù),也就是垃圾收集時(shí)間占 總時(shí)間的比率,相當(dāng)于是吞吐量的倒數(shù)。如果把此參數(shù)設(shè)置為19,那允許的最大GC時(shí) 間就占總時(shí)間的5% (即1/ (1 + 19)),默認(rèn)值為99,就是允許最大1% (即1 / (1+99)) 的垃圾收集時(shí)間。

由于與吞吐量關(guān)系密切,Parallel Scavenge收集器也經(jīng)常被稱為“吞吐量 優(yōu)先”收集器。除上述兩個(gè)參數(shù)之外,Parallel Scavenge收集器還有一個(gè)參 數(shù)-XX:+UseAdaptiveSizePolicy值得關(guān)注。這是一個(gè)開關(guān)參數(shù),當(dāng)這個(gè)參數(shù)打開之后,就 不需要手工指定新生代的大小?Xmn)EdenSurvivor區(qū)的比例?XX:SurvivorRatio)晉升老年代對(duì)象年齡?XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù)了,虛擬機(jī)會(huì)根據(jù)當(dāng) 前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或 最大的吞吐量,這種調(diào)節(jié)方式稱為GC自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics)

如果 讀者對(duì)于收集器運(yùn)作原理不太了解,手工優(yōu)化存在困難的時(shí)候,使用Parallel Scavenge 收集器配合自適應(yīng)調(diào)節(jié)策略,把內(nèi)存管理的調(diào)優(yōu)任務(wù)交給虛擬機(jī)去完成將是一個(gè)很 不錯(cuò)的選擇。只需要把基本的內(nèi)存數(shù)據(jù)設(shè)置好(如-Xmx設(shè)置最大堆),然后使用 MaxGCPauseMillis參數(shù)(更關(guān)注最大停頓時(shí)間)或GCTimeRatio參數(shù)(更關(guān)注吞吐量) 給虛擬機(jī)設(shè)立一個(gè)優(yōu)化目標(biāo),那具體細(xì)節(jié)參數(shù)的調(diào)節(jié)工作就由虛擬機(jī)完成了。自適應(yīng)調(diào) 節(jié)策略也是Parallel Scavenge收集器與ParNew收集器的一個(gè)重要區(qū)別。

新生代采用標(biāo)記-復(fù)制算法,老年代采用標(biāo)記-整理算法。

這是 JDK1.8 默認(rèn)收集器

使用 java -XX:+PrintCommandLineFlags -version 命令查看

-XX:InitialHeapSize=262921408 -XX:MaxHeapSize=4206742528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC java version "1.8.0_211" Java(TM) SE Runtime Environment (build 1.8.0_211-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)

JDK1.8 默認(rèn)使用的是 Parallel Scavenge + Parallel Old,如果指定了-XX:+UseParallelGC 參數(shù),則默認(rèn)指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 來禁用該功能?

Serial Old 收集器

Serial OldSerial收集器的老年代版本,它同樣是一個(gè)單線程收集器,使用“標(biāo) 記-整理”算法。

這個(gè)收集器的主要意義也是被Client模式下的虛擬機(jī)使用

如果 Server模式下,它主要還有兩大用途:一個(gè)是在JDK 1.5及之前的版本中與Parallel Scavenge收集器搭配使用。(需要說明一下, Parallel Scavenge收集器架構(gòu)中本身有PS MarkSweep收集器來進(jìn)行老年代收集,并非直接調(diào)用Serial old收集器,但是這個(gè)PS MarkSweep收集器與Serial old的實(shí)現(xiàn)幾乎是一樣的,所以在官方的許多資料中都是直接以Serial old代替PS MarkSweep進(jìn)行講解,這里筆者也采用這種方式。

另外一個(gè)就是作為CMS收集器的后備預(yù)案,在并發(fā)收集發(fā) 生Concurrent Mode Failure的時(shí)候使用。這兩點(diǎn)都將在后面的內(nèi)容中詳細(xì)講解。Serial Old收集器的工作過程如圖

Parallel Old 收集器

Parallel OldParallel Scavenge收集器的老年代版本使用多線程和標(biāo)記-整理算法。

這個(gè)收集器是在JDK 1.6中才開始提供的在此之前新生代的Parallel Scavenge 收集器一直處于比較尷尬的狀態(tài)。原因是,如果新生代選擇了 Parallel Scavenge收集器, 老年代除了 Serial Old (PS MarkSweep)收集器外別無(wú)選擇(還記得上面說過Parallel Scavenge收集器無(wú)法與CMS收集器配合工作嗎?)。由于單線程的老年代Serial Old收 集器在服務(wù)端應(yīng)用性能上的“拖累”,即便使用了 Parallel Scavenge收集器也未必能在整 體應(yīng)用上獲得吞吐量最大化的效果,又因?yàn)槔夏甏占袩o(wú)法充分利用服務(wù)器多CPU的 處理能力,在老年代很大而且硬件比較高級(jí)的環(huán)境中,這種組合的吞吐量甚至還不一定 有ParNewn CMS的組合“給力”。

直到Parallel Old收集器出現(xiàn)后,“吞吐量?jī)?yōu)先”收集器終于有了比較名副其實(shí)的應(yīng) 用組合,在注重吞吐量及CPU資源敏感的場(chǎng)合,都可以優(yōu)先考慮Parallel ScavengeParallel Old收集器。Parallel Old收集器的工作過程如圖

CMS收集器

CMS (Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收 集器。目前很大一部分的Java應(yīng)用都集中在互聯(lián)網(wǎng)站或B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用 尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間最短,以給用戶帶來較好的體驗(yàn)。CMS收 集器就非常符合這類應(yīng)用的需求。

CMS(Concurrent Mark Sweep)收集器是 HotSpot 虛擬機(jī)第一款真正意義上的并發(fā)收集器,它第一次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作。

從名字(包含“MarkSweep”)上就可以看出CMS收集器是基于“標(biāo)記-清除”算 法實(shí)現(xiàn)的,它的運(yùn)作過程相對(duì)于前面幾種收集器來說要更復(fù)雜一些,整個(gè)過程分為5個(gè) 步驟,包括:

  • 初始標(biāo)記(CMS initial mark)
  • 并發(fā)標(biāo)記(CMS concurrent mark)
  • 并發(fā)預(yù)清理
  • 重新標(biāo)記(CMS remark)
  • 并發(fā)清除(CMS concurrent sweep)

其中初始標(biāo)記、重新標(biāo)記這兩個(gè)步驟仍然需要“Stop The World”。(第一個(gè),第三個(gè)步驟暫停,第二個(gè),第四個(gè)并發(fā)

初始標(biāo)記僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,以及「年輕代」指向「老年代」的對(duì)象???????,速度很快,暫停

并發(fā)標(biāo)記階段就是進(jìn)行GC Roots Tracing的過程,同時(shí)開啟 GC 和用戶線程,用一個(gè)閉包結(jié)構(gòu)去記錄可達(dá)對(duì)象。但在這個(gè)階段結(jié)束,這個(gè)閉包結(jié)構(gòu)并不能保證包含當(dāng)前所有的可達(dá)對(duì)象。因?yàn)橛脩艟€程可能會(huì)不斷的更新引用域,所以 GC 線程無(wú)法保證可達(dá)性分析的實(shí)時(shí)性。所以這個(gè)算法里會(huì)跟蹤記錄這些發(fā)生引用更新的地方,并發(fā)。

「并發(fā)預(yù)處理」這個(gè)階段主要想干的事情:希望能減少下一個(gè)階段「重新標(biāo)記」所消耗的時(shí)間

因?yàn)橄乱粋€(gè)階段「重新標(biāo)記」是需要Stop The World的

「并發(fā)標(biāo)記」這個(gè)階段由于用戶線程是沒有被掛起的,所以對(duì)象是有可能發(fā)生變化的

可能有些對(duì)象,從新生代晉升到了老年代。可能有些對(duì)象,直接分配到了老年代(大對(duì)象)。可能老年代或者新生代的對(duì)象引用發(fā)生了變化…

那這個(gè)問題,怎么解決呢?

針對(duì)老年代的對(duì)象,其實(shí)還是可以借助類card table的存儲(chǔ)(將老年代對(duì)象發(fā)生變化所對(duì)應(yīng)的卡頁(yè)標(biāo)記為dirty)

所以「并發(fā)預(yù)處理」這個(gè)階段會(huì)掃描可能由于「并發(fā)標(biāo)記」時(shí)導(dǎo)致老年代發(fā)生變化的對(duì)象,會(huì)再掃描一遍標(biāo)記為dirty的卡頁(yè)

對(duì)于新生代的對(duì)象,我們還是得遍歷新生代來看看在「并發(fā)標(biāo)記」過程中有沒有對(duì)象引用了老年代..

不過JVM里給我們提供了很多「參數(shù)」,有可能在這個(gè)過程中會(huì)觸發(fā)一次 minor GC(觸發(fā)了minor GC 是意味著就可以更少地遍歷新生代的對(duì)象)

?

?而重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn) 作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間一般會(huì)比初始 標(biāo)記階段稍長(zhǎng)一些,但遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短,暫停。(注意hotspot算法細(xì)節(jié)里的增量更新)

這個(gè)過程的停頓時(shí)間其實(shí)很大程度上取決于上面「并發(fā)預(yù)處理」階段(可以發(fā)現(xiàn),這是一個(gè)追趕的過程:一邊在標(biāo)記存活對(duì)象,一邊用戶線程在執(zhí)行產(chǎn)生垃圾)

并發(fā)清除階段是使用標(biāo)記-清除的方法清理垃圾,注意不是標(biāo)記-整理!這個(gè)過程是并發(fā)的。

這個(gè)過程,還是有可能用戶線程在不斷產(chǎn)生垃圾,但只能留到下一次GC 進(jìn)行處理了,產(chǎn)生的這些垃圾被叫做“浮動(dòng)垃圾”

由于整個(gè)過程中耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除過程中,收集器線程都可以與用戶 線程一起工作,所以總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)地 執(zhí)行的。通過圖3-10可以比較清楚地看到CMS收集器的運(yùn)作步驟中并發(fā)和需要停頓的 時(shí)間。

CMS是一款優(yōu)秀的收集器,它的最主要優(yōu)點(diǎn)在名字上已經(jīng)體現(xiàn)出來了:并發(fā)收集、 低停頓Sun的一些官方文檔里面也稱之為并發(fā)低停頓收集器(Concurrent Low Pause Collector)但是CMS還遠(yuǎn)達(dá)不到完美的程度,它有以下四個(gè)顯著的缺點(diǎn):

  • 對(duì) CPU 資源敏感
  • 無(wú)法處理浮動(dòng)垃圾,可能會(huì)導(dǎo)致serial old
  • 它使用的回收算法-“標(biāo)記-清除”算法會(huì)導(dǎo)致收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生
  • 需要預(yù)留的空間,給用戶線程使用

CMS收集器對(duì)CPU資源非常敏感。其實(shí),面向并發(fā)設(shè)計(jì)的程序都對(duì)CPU資源比 較敏感。在并發(fā)階段,它雖然不會(huì)導(dǎo)致用戶線程停頓,但是會(huì)因?yàn)檎加昧艘徊糠?線程(或者說CPU資源)而導(dǎo)致應(yīng)用程序變慢,總吞吐量會(huì)降低。CMS默認(rèn)啟 動(dòng)的回收線程數(shù)是(CPU數(shù)量+3)/4,也就是當(dāng)CPU4個(gè)以上時(shí),并發(fā)回收 時(shí)垃圾收集線程最多占用不超過25%CPU資源。但是當(dāng)CPU不足4個(gè)時(shí)(譬 如2個(gè)),那么CMS對(duì)用戶程序的影響就可能變得很大,如果CPU負(fù)載本來就 比較大的時(shí)候,還分出一半的運(yùn)算能力去執(zhí)行收集器線程,就可能導(dǎo)致用戶程序 的執(zhí)行速度忽然降低了 50%,這也很讓人受不了。為了解決這種情況,虛擬機(jī)提 供了一種稱為“增量式并發(fā)收集器” (Incremental Concurrent Mark Sweep / i-CMS) CMS收集器變種,所做的事情和單CPU年代PC機(jī)操作系統(tǒng)使用搶占式來模 擬多任務(wù)機(jī)制的思想一樣,就是在并發(fā)標(biāo)記和并發(fā)清理的時(shí)候讓GC線程、用戶 線程交替運(yùn)行,盡量減少GC線程的獨(dú)占資源的時(shí)間,這樣整個(gè)垃圾收集的過程 會(huì)更長(zhǎng),但對(duì)用戶程序的影響就會(huì)顯得少一些,速度下降也就沒有那么明顯,但 是目前版本中,i?CMS已經(jīng)被聲明為“deprecated",即不再提倡用戶使用。

CMS收集器無(wú)法處理浮動(dòng)垃圾(Floating Garbage),可能出現(xiàn)“Concurrent Mode Failure"失敗而導(dǎo)致另一次Full GC的產(chǎn)生。由于CMS并發(fā)清理階段用戶線程還 在運(yùn)行著,伴隨程序的運(yùn)行自然還會(huì)有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在 標(biāo)記過程之后,CMS無(wú)法在本次收集中處理掉它們,只好留待下一次GC時(shí)再將 其清理掉。這一部分垃圾就稱為“浮動(dòng)垃圾”。

也是由于在垃圾收集階段用戶線 程還需要運(yùn)行,即還需要預(yù)留足夠的內(nèi)存空間給用戶線程使用,因此CMS收集 器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進(jìn)行收集,需要預(yù)留一 部分空間提供并發(fā)收集時(shí)的程序運(yùn)作使用。在默認(rèn)設(shè)置下,CMS收集器在老年代 使用了 68%的空間后就會(huì)被激活,這是一個(gè)偏保守的設(shè)置,如果在應(yīng)用中老年代 增長(zhǎng)不是太快,可以適當(dāng)調(diào)高參數(shù)-XX:CMSInitiatingOccupancyFraction的值來 提髙觸發(fā)百分比,以便降低內(nèi)存回收次數(shù)以獲取更好的性能。

要是CMS運(yùn)行期 間預(yù)留的內(nèi)存無(wú)法滿足程序需要,就會(huì)出現(xiàn)一次“Concurrent Mode Failure”失 敗,這時(shí)候虛擬機(jī)將啟動(dòng)后備預(yù)案:臨時(shí)啟用Serial Old收集器來重新進(jìn)行老年 代的垃圾收集,這樣停頓時(shí)間就很長(zhǎng)了。所以說參數(shù)-XX:CMSInitiatingOccupan cyFraction設(shè)置得太高將會(huì)很容易導(dǎo)致大量uConcurrent Mode Failure”失敗,性 能反而降低。

還有最后一個(gè)缺點(diǎn),在本節(jié)的開頭曾提到, CMS是一款基于"標(biāo)記-清除”算法實(shí)現(xiàn)的收集器,如果讀者對(duì)前面這部分介紹還有印象的話,就可能想到這意味著收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生。空間碎片過多時(shí),將會(huì)給大對(duì)象分配帶來很大麻煩,往往會(huì)出現(xiàn)老年代還有很多剩余空間,但就是無(wú)法找到足夠大的連續(xù)空間來分配當(dāng)前對(duì)象,而不得不提前觸發(fā)一次Full GC的情況。

為了解決這個(gè)問題,CMS收集器提供了一個(gè)-xx: +UseCMS-CompactAtFullCcollection開關(guān)參數(shù)(默認(rèn)是開啟的,此參數(shù)從- JDK 9開始廢棄) ,用于在CMS收集器不得不進(jìn)行FullGC時(shí)開啟內(nèi)存碎片的合并整理過程,由于這個(gè)內(nèi)存整理必須移動(dòng)存活對(duì)象, (在Shenandoah和ZGc出現(xiàn)前)是無(wú)法并發(fā)的。這樣空間碎片問題是解決了,但停頓時(shí)間又會(huì)變長(zhǎng)

因此虛擬機(jī)設(shè)計(jì)者們還提供了另外一個(gè)參數(shù)-xx: CMSFullGCsBeforeCompaction (此參數(shù)從JDK 9開始廢棄) ,這個(gè)參數(shù)的作用是要求CMs收集器在執(zhí)行過若干次(數(shù)量由參數(shù)值決定)不整理空間的Full GC之后,下一次進(jìn)入Full GC前會(huì)先進(jìn)行碎片整理(默認(rèn)值為0,表示每次進(jìn)入Full GC時(shí)都進(jìn)行碎片整理)

G1收集器

G1簡(jiǎn)介

G1 (Garbage-First) 是一款面向服務(wù)器的垃圾收集器,主要針對(duì)配備多顆處理器及大容量?jī)?nèi)存的機(jī)器. 以極高概率滿足 GC 停頓時(shí)間要求的同時(shí),還具備高吞吐量性能特征.

HotSpot開發(fā)團(tuán)隊(duì)最初賦予它的期望是(在比較長(zhǎng)期的)未來可以替換掉JDK5中發(fā)布的CMS收集器。現(xiàn)在這個(gè)期望目標(biāo)已經(jīng)實(shí)現(xiàn)過半了, JDK9發(fā)布之日, G1宣告取代Parallel Scavenge加Parallel Old組合,成為服務(wù)端模式下的默認(rèn)垃圾收集器,而CMs則淪落至被聲明為不推薦使用(Deprecate)的收集器。如果對(duì)JDK 9及以上版本的HotSpot虛擬機(jī)使用參數(shù)-XX: +UseConcMarkSweepGC來開啟CMS收集器的話,用戶會(huì)收到一個(gè)警告信息,提示CMS未來將會(huì)被廢棄

作為CMS收集器的替代者和繼承人,設(shè)計(jì)者們希望做出一款能夠建立起“停頓時(shí)間模型” (PausePrediction Model)的收集器,停頓時(shí)間模型的意思是能夠支持指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間大概率不超過N毫秒這樣的目標(biāo),這幾乎已經(jīng)是實(shí)時(shí)Java (RTSJ)的中軟實(shí)時(shí)垃圾收集器特征了。

那具體要怎么做才能實(shí)現(xiàn)這個(gè)目標(biāo)呢?首先要有一個(gè)思想上的改變,在G1收集器出現(xiàn)之前的所有其他收集器,包括CMS在內(nèi),垃圾收集的目標(biāo)范圍要么是整個(gè)新生代(Minor GC) ,要么就是整個(gè)老年代(Major GC) ,再要么就是整個(gè)Java堆(Full GC)。而G1跳出了這個(gè)樊籠,它可以面向堆內(nèi)存任何部分來組成回收集(Collection Set,一般簡(jiǎn)稱CSet)進(jìn)行回收,衡量標(biāo)準(zhǔn)不再是它屬于哪個(gè)分代,而是哪塊內(nèi)存中存放的垃圾數(shù)量最多,回收收益最大,這就是G1收集器的Mixed GC模式。

G1 收集器采用一種不同的方式來管理堆內(nèi)存.

堆內(nèi)存被劃分為多個(gè)大小相等的 heap 區(qū),每個(gè)heap區(qū)都是邏輯上連續(xù)的一段內(nèi)存(virtual memory). 其中一部分區(qū)域被當(dāng)成收集器相同的角色(eden, survivor, old), 但每個(gè)角色的區(qū)域個(gè)數(shù)都不是固定的。這在內(nèi)存使用上提供了更多的靈活性。

Region中還有一類特殊的Humongous區(qū)域,專門用來存儲(chǔ)大對(duì)象。G1認(rèn)為只要大小超過了一個(gè)Region容量一半的對(duì)象即可判定為大對(duì)象。每個(gè)Region的大小可以通過參數(shù)-xx: G1HeapRegionSize設(shè)·定,取值范圍為1MB~32MB,且應(yīng)為2的N次冪。而對(duì)于那些超過了整個(gè)Region容量的超級(jí)大對(duì)象,將會(huì)被存放在N個(gè)連續(xù)的Humongous Region之中, G1的大多數(shù)行為都把Humongous Regon作為老年代的一部分來進(jìn)行看待,如圖所示。

G1細(xì)節(jié)

1 將Java堆分成多個(gè)獨(dú)立Region后, Region里面存在的跨Region引用對(duì)象如何解決?

解決的思路我們已經(jīng)知道 :使用記憶集避免全堆作為GC?Roots掃描,但在G1收集器上記億集的應(yīng)用其實(shí)要復(fù)雜很多,它的每個(gè)Region都維護(hù)有自己的記憶集,這些記憶集會(huì)記錄下別的Region指向自己的指針,并標(biāo)記這些指針分別在哪些卡頁(yè)的范圍之內(nèi)。

G1的記憶集在存儲(chǔ)結(jié)構(gòu)的本質(zhì)上是一種哈希表, Key是別的Region的起始地址, Value是一個(gè)集合,里面存儲(chǔ)的元素是卡表的索引號(hào)。這種“雙向”的卡表結(jié)構(gòu)(卡表是“我指向誰(shuí)”,這種結(jié)構(gòu)還記錄了“誰(shuí)指向我”)比原來的卡表實(shí)現(xiàn)起來更復(fù)雜

同時(shí)由于Region數(shù)量比傳統(tǒng)收集器的分代數(shù)量明顯要多得多,因此G1收集器要比其他的傳統(tǒng)垃圾收集器有著更高的內(nèi)存占用負(fù)擔(dān)。根據(jù)經(jīng)驗(yàn), G1至少要耗費(fèi)大約相當(dāng)于Java堆容量10%至20%的額外內(nèi)存來維持收集器工作。

2 在并發(fā)標(biāo)記階段如何保證收集線程與用戶線程互不干擾地運(yùn)行?

這里首先要解決的是用戶線程改變對(duì)象引用關(guān)系時(shí),必須保證其不能打破原本的對(duì)象圖結(jié)構(gòu),導(dǎo)致標(biāo)記結(jié)果出現(xiàn)錯(cuò)誤,該問題的解決辦法筆者已經(jīng)講解過 : CMS收集器采用增量更新算法實(shí)現(xiàn),而G1收集器則是通過原始快照(SATB)算法來實(shí)現(xiàn)的。

此外,垃圾收集對(duì)用戶線程的影響還體現(xiàn)在回收過程中新創(chuàng)建對(duì)象的內(nèi)存分配上,程序要繼續(xù)運(yùn)行就肯定會(huì)持續(xù)有新對(duì)象被創(chuàng)建, G1為每一個(gè)Region設(shè)計(jì)了兩個(gè)名為TAMS (Top at Mark Start)的指針,把Region中的一部分空間劃分出來用于并發(fā)回收過程中的新對(duì)象分配,并發(fā)回收時(shí)新分配的對(duì)象地址都必須要在這兩個(gè)指針位置以上。

G1收集器默認(rèn)在這個(gè)地址以上的對(duì)象是被隱式標(biāo)記過的,即默認(rèn)它們是存活的,不納入回收范圍。與CMS中的"Concurrent Mode Failure"失敗會(huì)導(dǎo)致Full GC類似,如果內(nèi)存回收的速度趕不上內(nèi)存分配的速度,G1收集器也要被迫凍結(jié)用戶線程執(zhí)行,導(dǎo)致Full GC而產(chǎn)生長(zhǎng)時(shí)間"Stop The World"。

3 怎樣建立起可靠的停頓預(yù)測(cè)模型?

用戶通過-xx: MaxGCPauseMillis參數(shù)指定的停頓時(shí)間只意味著垃圾收集發(fā)生之前的期望值,但G1收集器要怎么做才能滿足用戶的期望呢?

G1收集器的停頓預(yù)測(cè)模型是以衰減均值(Decaying Average)為理論基礎(chǔ)來實(shí)現(xiàn)的,在垃圾收集過程中, G1收集器會(huì)記錄每個(gè)Region的回收耗時(shí)、每個(gè)Regon記憶集里的臟卡數(shù)量等各個(gè)可測(cè)量的步驟花費(fèi)的成本,并分析得出平均值、標(biāo)準(zhǔn)偏差、置信度等統(tǒng)計(jì)信息。

這里強(qiáng)調(diào)的"衰減平均值”是指它會(huì)比普通的平均值更容易受到新數(shù)據(jù)的影響,平均值代表整體平均狀態(tài),但衰減平均值更準(zhǔn)確地代表“最近的"平均狀態(tài)。換句話說, Region的統(tǒng)計(jì)狀態(tài)越新越能決定其回收的價(jià)值。然后通過這些信息預(yù)測(cè)現(xiàn)在開始回收的話,由哪些Region組成回收集才可以在不超過期望停頓時(shí)間的約束下獲得最高的收益。

G1 Minor GC流程

G1的Minor GC其實(shí)觸發(fā)時(shí)機(jī)跟前面提到過的垃圾收集器都是一樣的

等到Eden區(qū)滿了之后,會(huì)觸發(fā)Minor GC。Minor GC同樣也是會(huì)發(fā)生Stop The World的

要補(bǔ)充說明的是:在G1的世界里,新生代和老年代所占堆的空間是沒那么固定的(會(huì)動(dòng)態(tài)根據(jù)「最大停頓時(shí)間」進(jìn)行調(diào)整)

這塊要知道會(huì)給我們提供參數(shù)進(jìn)行配置就好了

所以,動(dòng)態(tài)地改變年輕代Region的個(gè)數(shù)可以「控制」Minor GC的開銷

Minor GC我認(rèn)為可以簡(jiǎn)單分為為三個(gè)步驟:根掃描、更新&&處理 RSet、復(fù)制對(duì)象

第一步應(yīng)該很好理解,因?yàn)檫@跟之前CMS是類似的,可以理解為初始標(biāo)記的過程

第二步涉及到「Rset」的概念

從上一次我們聊CMS回收過程的時(shí)候,同樣講到了Minor GC,它是通過「卡表」(cart table)來避免全表掃描老年代的對(duì)象

因?yàn)镸inor GC 是回收年輕代的對(duì)象,但如果老年代有對(duì)象引用著年輕代,那這些被老年代引用的對(duì)象也不能回收掉

同樣的,在G1也有這種問題(畢竟是Minor GC)。CMS是卡表,而G1解決「跨代引用」的問題的存儲(chǔ)一般叫做RSet

只要記住,RSet這種存儲(chǔ)在每個(gè)Region都會(huì)有,它記錄著「其他Region引用了當(dāng)前Region的對(duì)象關(guān)系」

對(duì)于年輕代的Region,它的RSet 只保存了來自老年代的引用(因?yàn)槟贻p代的沒必要存儲(chǔ)啊,自己都要做Minor GC了)

而對(duì)于老年代的 Region 來說,它的 RSet 也只會(huì)保存老年代對(duì)它的引用(在G1垃圾收集器,老年代回收之前,都會(huì)先對(duì)年輕代進(jìn)行回收,所以沒必要保存年輕代的引用)

那第二步看完RSet的概念,應(yīng)該也好理解了吧?

無(wú)非就是處理RSet的信息并且掃描,將老年代對(duì)象持有年輕代對(duì)象的相關(guān)引用都加入到GC Roots下,避免被回收掉

到了第三步也挺好理解的:把掃描之后存活的對(duì)象往「空的Survivor區(qū)」或者「老年代」存放,其他的Eden區(qū)進(jìn)行清除

在G1還有另一個(gè)名詞,叫做CSet。

它的全稱是 Collection Set,保存了一次GC中「將執(zhí)行垃圾回收」的Region。CSet中的所有存活對(duì)象都會(huì)被轉(zhuǎn)移到別的可用Region上

在Minor GC 的最后,會(huì)處理下軟引用、弱引用、JNI Weak等引用,結(jié)束收集

?

G1 Mixed GC流程

如果我們不去計(jì)算用戶線程運(yùn)行過程中的動(dòng)作(如使用寫屏障維護(hù)記憶集的操作) , G1收集器的運(yùn)作過程大致可劃分為以下四個(gè)步驟:

?

初始標(biāo)記(Initial Marking) :僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,并且修改TAMS指針的值,讓下一階段用戶線程并發(fā)運(yùn)行時(shí),能正確地在可用的Region中分配新對(duì)象。這個(gè)階段需要停頓線程,但耗時(shí)很短,而且是借用進(jìn)行Minor GC的時(shí)候同步完成的,所以G1收集器在這個(gè)階段實(shí)際并沒有額外的停頓。

這個(gè)過程是「共用」了Minor GC的 Stop The World(Mixed GC 一定會(huì)發(fā)生 Minor GC),復(fù)用了「掃描GC Roots」的操作。并且在這個(gè)過程中,老年代和新生代都會(huì)掃。

并發(fā)標(biāo)記(Concurrent Marking) :從GC Root開始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,遞歸掃描整個(gè)堆里的對(duì)象圖,找出要回收的對(duì)象,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行。當(dāng)對(duì)象圖掃描完成以后,還要重新處理SATB記錄下的在并發(fā)時(shí)有引用變動(dòng)的對(duì)象。

最終標(biāo)記(Final Marking) :對(duì)用戶線程做另一個(gè)短暫的暫停,用于處理并發(fā)階段結(jié)束后仍遺留下來的最后那少量的SATB記錄。如果在開始時(shí),G1就認(rèn)為它是活的,那就在此次GC中不會(huì)對(duì)它回收,即便可能在「并發(fā)階段」上對(duì)象已經(jīng)變?yōu)榱死?/p>

所以,G1也有可能會(huì)存在「浮動(dòng)垃圾」的問題。但是總的來說,對(duì)于G1而言,問題不大(畢竟它不是追求一次把所有的垃圾都清除掉,而是注重 Stop The World時(shí)間)

標(biāo)記階段完成后,G1就可以知道哪些heap區(qū)的empty空間最大。

G1 收集器在后臺(tái)維護(hù)了一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先選擇回收價(jià)值最大的 Region(這也就是它的名字 Garbage-First 的由來) 。這種使用 Region 劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式,保證了 G1 收集器在有限時(shí)間內(nèi)可以盡可能高的收集效率(把內(nèi)存化整為零)。

G1使用暫停預(yù)測(cè)模型(pause prediction model)來達(dá)到用戶定義的目標(biāo)暫停時(shí)間,并根據(jù)目標(biāo)暫停時(shí)間來選擇此次進(jìn)行垃圾回收的heap區(qū)域數(shù)量.

需要強(qiáng)調(diào)的是, G1并不是一款實(shí)時(shí)垃圾收集器(real-time collector). 能以極高的概率在設(shè)定的目標(biāo)暫停時(shí)間內(nèi)完成,但不保證絕對(duì)在這個(gè)時(shí)間內(nèi)完成。 基于以前收集的各種監(jiān)控?cái)?shù)據(jù), G1會(huì)根據(jù)用戶指定的目標(biāo)時(shí)間來預(yù)估能回收多少個(gè)heap區(qū). 因此,收集器有一個(gè)相當(dāng)精確的heap區(qū)耗時(shí)計(jì)算模型,并根據(jù)該模型來確定在給定時(shí)間內(nèi)去回收哪些heap區(qū)

篩選回收(Live Data Counting and Evacuation) :負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時(shí)間來制定回收計(jì)劃,可以自由選擇任意多個(gè)Region構(gòu)成回收集,然后把決定回收的那一部分Region的存活對(duì)象復(fù)制到空的Region中,再清理掉整個(gè)舊Region的全部空間。這里的操作涉及存活對(duì)象的移動(dòng),是必須暫停用戶線程,由多條收集器線程并行完成的。

一般來說,Mixed GC會(huì)選定所有的年輕代Region,部分「回收價(jià)值高」的老年代Region(回收價(jià)值高其實(shí)就是垃圾多)進(jìn)行采集

被G1標(biāo)記為適合回收的heap區(qū)將使用轉(zhuǎn)移(evacuation)的方式進(jìn)行垃圾回收. G1將一個(gè)或多個(gè)heap區(qū)域中的對(duì)象拷貝到其他的單個(gè)區(qū)域中,并在此過程中壓縮和釋放內(nèi)存,基于標(biāo)記-整理

那G1會(huì)什么時(shí)候發(fā)生full GC?

如果在Mixed GC中無(wú)法跟上用戶線程分配內(nèi)存的速度,導(dǎo)致老年代填滿無(wú)法繼續(xù)進(jìn)行Mixed GC,就又會(huì)降級(jí)到serial old GC來收集整個(gè)GC heap

不過這個(gè)場(chǎng)景相較于CMS還是很少的,畢竟G1沒有CMS內(nèi)存碎片這種問題

從上述階段的描述可以看出, G1收集器除了并發(fā)標(biāo)記外,其余階段也是要完全暫停用戶線程的,換言之,它并非純粹地追求低延遲,官方給它設(shè)定的目標(biāo)是在延遲可控的情況下獲得盡可能高的吞吐量,所以才能擔(dān)當(dāng)起“全功能收集器”的重任與期望

毫無(wú)疑問,可以由用戶指定期望的停頓時(shí)間是G1收集器很強(qiáng)大的一個(gè)功能,設(shè)置不同的期望停頓時(shí)間,可使得G1在不同應(yīng)用場(chǎng)景中取得關(guān)注吞吐量和關(guān)注延遲之間的最佳平衡。不過,這里設(shè)置的“期望值”必須是符合實(shí)際的,不能異想天開,畢竟G1是要凍結(jié)用戶線程來復(fù)制對(duì)象的,這個(gè)停頓時(shí)間再怎么低也得有個(gè)限度。它默認(rèn)的停頓目標(biāo)為兩百毫秒,一般來說,回收階段占到幾十到一百甚至接近兩百毫秒都很正常,但如果我們把停頓時(shí)間調(diào)得非常低,譬如設(shè)置為二十毫秒,很可能出現(xiàn)的結(jié)果就是由于停頓目標(biāo)時(shí)間太短,導(dǎo)致每次選出來的回收集只占堆內(nèi)存很小的一部分,收集器收集的速度逐漸跟不上分配器分配的速度,導(dǎo)致垃圾慢慢堆積。很可能一開始收集器還能從空閑的堆內(nèi)存中獲得一些喘息的時(shí)間,但應(yīng)用運(yùn)行時(shí)間一長(zhǎng)就不行了,最終占滿堆引發(fā)Full GC反而降低性能,所以通常把期望停頓時(shí)間設(shè)置為一兩百毫秒或者兩三百毫秒會(huì)是比較合理的。

從G1開始,最先進(jìn)的垃圾收集器的設(shè)計(jì)導(dǎo)向都不約而同地變?yōu)樽非竽軌驊?yīng)付應(yīng)用的內(nèi)存分配速率Allocation Rate) ,而不追求一次把整個(gè)Java堆全部清理干凈。這樣,應(yīng)用在分配,同時(shí)收集器在收集,只要收集的速度能跟得上對(duì)象分配的速度,那一切就能運(yùn)作得很完美。這種新的收集器設(shè)計(jì)思路從工程實(shí)現(xiàn)上看是從G1開始興起的,所以說G1是收集器技術(shù)發(fā)展的一個(gè)里程碑。

G1特點(diǎn)

它具備一下特點(diǎn):

  • 并行與并發(fā):G1 能充分利用 CPU、多核環(huán)境下的硬件優(yōu)勢(shì),使用多個(gè) CPU(CPU 或者 CPU 核心)來縮短 Stop-The-World 停頓時(shí)間。部分其他收集器原本需要停頓 Java 線程執(zhí)行的 GC 動(dòng)作,G1 收集器仍然可以通過并發(fā)的方式讓 java 程序繼續(xù)執(zhí)行。
  • 內(nèi)存分區(qū):將內(nèi)存劃分為一個(gè)個(gè)相等大小的內(nèi)存分區(qū),回收時(shí)則以分區(qū)為單位進(jìn)行回收,存活的對(duì)象復(fù)制到另一個(gè)空閑分區(qū)中。由于都是以相等大小的分區(qū)為單位進(jìn)行操作,因此G1天然就是一種壓縮方案(局部壓縮);
  • 分代收集:雖然 G1 可以不需要其他收集器配合就能獨(dú)立管理整個(gè) GC 堆,但是還是保留了分代的概念。G1只有邏輯上的分代概念,或者說每個(gè)分區(qū)都可能隨G1的運(yùn)行在不同代之間前后切換;
  • 無(wú)空間碎片:與 CMS 的“標(biāo)記-清理”算法不同,G1 從整體來看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器;從局部上來看是基于“標(biāo)記-復(fù)制”算法實(shí)現(xiàn)的。
  • 不一定內(nèi)存耗盡才GC:G1的設(shè)計(jì)原則是"首先收集盡可能多的垃圾(Garbage First)"。因此,G1并不會(huì)等內(nèi)存耗盡(串行、并行)或者快耗盡(CMS)的時(shí)候開始垃圾收集,而是在內(nèi)部采用了啟發(fā)式算法,在老年代找出具有高收集收益的分區(qū)進(jìn)行收集。同時(shí)G1可以根據(jù)用戶設(shè)置的暫停時(shí)間目標(biāo)自動(dòng)調(diào)整年輕代和總堆大小,暫停目標(biāo)越短年輕代空間越小、總空間就越大;
  • 可預(yù)測(cè)的停頓:這是 G1 相對(duì)于 CMS 的另一個(gè)大優(yōu)勢(shì),降低停頓時(shí)間是 G1 和 CMS 共同的關(guān)注點(diǎn),但 G1 除了追求低停頓外,還能建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長(zhǎng)度為 M 毫秒的時(shí)間片段內(nèi)。
  • 允許部分收集:G1的收集都是STW(低停頓)的,但年輕代和老年代的收集界限比較模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年輕代分區(qū)(年輕代收集),也可能在收集年輕代的同時(shí),包含部分老年代分區(qū)(混合收集),這樣即使堆內(nèi)存很大時(shí),也可以限制收集范圍,從而降低停頓。

G1與CMS的區(qū)別與選擇

G1收集器是垃圾收集器理論進(jìn)一步發(fā)展的產(chǎn)物,它與前面的CMS收集器相比有兩 個(gè)顯著的改進(jìn):

1?G1收集器是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器,也就是說它 不會(huì)產(chǎn)生空間碎片,這對(duì)于長(zhǎng)時(shí)間運(yùn)行的應(yīng)用系統(tǒng)來說非常重要。

2?它可以非常精確 地控制停頓,既能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收 集上的時(shí)間不得超過N毫秒,這幾乎已經(jīng)是實(shí)時(shí)Java (RTSJ)的垃圾收集器的特征了

也有缺點(diǎn)

用戶程序運(yùn)行過程中, G1無(wú)論是為了垃圾收集產(chǎn)生的內(nèi)存占用(Footprint)還是程序運(yùn)行時(shí)的額外執(zhí)行負(fù)載(Overload)都要比CMS要高。

1 內(nèi)存占用來說,雖然G1和CMS都使用卡表來處理跨代指針,但G1的卡表實(shí)現(xiàn)更為復(fù)雜,而且堆中每個(gè)Region,無(wú)論扮演的是新生代還是老年代角色,都必須有一份卡表,這導(dǎo)致G1的記憶集(和其他內(nèi)存消耗)可能會(huì)占整個(gè)堆容量的20%乃至更多的內(nèi)存空間;

相比起來CMS的卡表就相當(dāng)簡(jiǎn)單,只有唯一一份,而且只需要處理老年代到新生代的引用,反過來則不需要,由于新生代的對(duì)象具有朝生夕滅的不穩(wěn)定性,引用變化頻繁,能省下這個(gè)區(qū)域的維護(hù)開銷是很劃算的

2 在執(zhí)行負(fù)載的角度上,同樣由于兩個(gè)收集器各自的細(xì)節(jié)實(shí)現(xiàn)特點(diǎn)導(dǎo)致了用戶程序運(yùn)行時(shí)的負(fù)載會(huì)有不同,譬如它們都使用到寫屏障, CMS用寫后屏障來更新維護(hù)卡表;

而G1除了使用寫后屏障來進(jìn)行同樣的(由于G1的卡表結(jié)構(gòu)復(fù)雜,其實(shí)是更煩瑣的)卡表維護(hù)操作外,為了實(shí)現(xiàn)原始快照搜索(SATB)算法,還需要使用寫前屏障來跟蹤并發(fā)時(shí)的指針變化情況。

相比起增量更新算法,原始快照搜索能夠減少并發(fā)標(biāo)記和重新標(biāo)記階段的消耗,避免CMS那樣在最終標(biāo)記階段停頓時(shí)間過長(zhǎng)的缺點(diǎn),但是在用戶程序運(yùn)行過程中確實(shí)會(huì)產(chǎn)生由跟蹤引用變化帶來的額外負(fù)擔(dān)。

由于G1對(duì)寫屏障的復(fù)雜操作要比CMS消耗更多的運(yùn)算資源,所以CMS的寫屏障實(shí)現(xiàn)是直接的同步操作,而G1就不得不將其實(shí)現(xiàn)為類似于消息隊(duì)列的結(jié)構(gòu),把寫前屏障和寫后屏障中要做的事情都放到隊(duì)列里,然后再異步處理。

以上的優(yōu)缺點(diǎn)對(duì)比僅僅是針對(duì)G1和CMS兩款垃圾收集器單獨(dú)某方面的實(shí)現(xiàn)細(xì)節(jié)的定性分析,通常我們說哪款收集器要更好、要好上多少,往往是針對(duì)具體場(chǎng)景才能做的定量比較。按照筆者的實(shí)踐經(jīng)驗(yàn),目前在小內(nèi)存應(yīng)用上CMS的表現(xiàn)大概率仍然要會(huì)優(yōu)于G1,而在大內(nèi)存應(yīng)用上G1則大多能發(fā)揮其優(yōu)勢(shì),這個(gè)優(yōu)劣勢(shì)的Java堆容量平衡點(diǎn)通常在6GB至8GB之間,當(dāng)然,以上這些也僅是經(jīng)驗(yàn)之談,不同應(yīng)用需要量體裁衣地實(shí)際測(cè)試才能得出最合適的結(jié)論,隨著HotSpot的開發(fā)者對(duì)G1的不斷優(yōu)化,也會(huì)讓對(duì)比結(jié)果繼續(xù)向G1傾斜。

ZGC收集器

ZGC(The Z Garbage Collector)是JDK 11中推出的一款低延遲垃圾回收器,它的設(shè)計(jì)目標(biāo)包括:

停頓時(shí)間不超過10ms;

停頓時(shí)間不會(huì)隨著堆的大小,或者活躍對(duì)象的大小而增加;

支持8MB~4TB級(jí)別的堆(未來支持16TB)。

從設(shè)計(jì)目標(biāo)來看,我們知道ZGC適用于大內(nèi)存低延遲服務(wù)的內(nèi)存管理和回收。

與CMS中的ParNew和G1類似,ZGC也采用標(biāo)記-復(fù)制算法,不過ZGC對(duì)該算法做了重大改進(jìn):ZGC在標(biāo)記、轉(zhuǎn)移和重定位階段幾乎都是并發(fā)的,這是ZGC實(shí)現(xiàn)停頓時(shí)間小于10ms目標(biāo)的最關(guān)鍵原因。

ZGC垃圾回收周期如下圖所示:

ZGC只有三個(gè)STW階段:初始標(biāo)記,再標(biāo)記,初始轉(zhuǎn)移。其中,初始標(biāo)記和初始轉(zhuǎn)移分別都只需要掃描所有GC Roots,其處理時(shí)間和GC Roots的數(shù)量成正比,一般情況耗時(shí)非常短;再標(biāo)記階段STW時(shí)間很短,最多1ms,超過1ms則再次進(jìn)入并發(fā)標(biāo)記階段。即,ZGC幾乎所有暫停都只依賴于GC Roots集合大小,停頓時(shí)間不會(huì)隨著堆的大小或者活躍對(duì)象的大小而增加。與ZGC對(duì)比,G1的轉(zhuǎn)移階段完全STW的,且停頓時(shí)間隨存活對(duì)象的大小增加而增加。

ZGC關(guān)鍵技術(shù)

ZGC通過著色指針和讀屏障技術(shù),解決了轉(zhuǎn)移過程中準(zhǔn)確訪問對(duì)象的問題,實(shí)現(xiàn)了并發(fā)轉(zhuǎn)移。大致原理描述如下:并發(fā)轉(zhuǎn)移中“并發(fā)”意味著GC線程在轉(zhuǎn)移對(duì)象的過程中,應(yīng)用線程也在不停地訪問對(duì)象。假設(shè)對(duì)象發(fā)生轉(zhuǎn)移,但對(duì)象地址未及時(shí)更新,那么應(yīng)用線程可能訪問到舊地址,從而造成錯(cuò)誤。而在ZGC中,應(yīng)用線程訪問對(duì)象將觸發(fā)“讀屏障”,如果發(fā)現(xiàn)對(duì)象被移動(dòng)了,那么“讀屏障”會(huì)把讀出來的指針更新到對(duì)象的新地址上,這樣應(yīng)用線程始終訪問的都是對(duì)象的新地址。那么,JVM是如何判斷對(duì)象被移動(dòng)過呢?就是利用對(duì)象引用的地址,即著色指針。下面介紹著色指針和讀屏障技術(shù)細(xì)節(jié)。

著色指針

著色指針是一種將信息存儲(chǔ)在指針中的技術(shù)。

ZGC僅支持64位系統(tǒng),它把64位虛擬地址空間劃分為多個(gè)子空間,如下圖所示:

其中,[0~4TB) 對(duì)應(yīng)Java堆,[4TB ~ 8TB) 稱為M0地址空間,[8TB ~ 12TB) 稱為M1地址空間,[12TB ~ 16TB) 預(yù)留未使用,[16TB ~ 20TB) 稱為Remapped空間。

當(dāng)應(yīng)用程序創(chuàng)建對(duì)象時(shí),首先在堆空間申請(qǐng)一個(gè)虛擬地址,但該虛擬地址并不會(huì)映射到真正的物理地址。ZGC同時(shí)會(huì)為該對(duì)象在M0、M1和Remapped地址空間分別申請(qǐng)一個(gè)虛擬地址,且這三個(gè)虛擬地址對(duì)應(yīng)同一個(gè)物理地址,但這三個(gè)空間在同一時(shí)間有且只有一個(gè)空間有效。ZGC之所以設(shè)置三個(gè)虛擬地址空間,是因?yàn)樗褂谩翱臻g換時(shí)間”思想,去降低GC停頓時(shí)間。“空間換時(shí)間”中的空間是虛擬空間,而不是真正的物理空間。后續(xù)章節(jié)將詳細(xì)介紹這三個(gè)空間的切換過程。

與上述地址空間劃分相對(duì)應(yīng),ZGC實(shí)際僅使用64位地址空間的第0~41位,而第42~45位存儲(chǔ)元數(shù)據(jù),第47~63位固定為0。

?

ZGC將對(duì)象存活信息存儲(chǔ)在42~45位中,這與傳統(tǒng)的垃圾回收并將對(duì)象存活信息放在對(duì)象頭中完全不同。

讀屏障

讀屏障是JVM向應(yīng)用代碼插入一小段代碼的技術(shù)。當(dāng)應(yīng)用線程從堆中讀取對(duì)象引用時(shí),就會(huì)執(zhí)行這段代碼。需要注意的是,僅“從堆中讀取對(duì)象引用”才會(huì)觸發(fā)這段代碼。

讀屏障示例:

Object o = obj.FieldA ? // 從堆中讀取引用,需要加入屏障
<Load barrier>
Object p = o ?// 無(wú)需加入屏障,因?yàn)椴皇菑亩阎凶x取引用
o.dosomething() // 無(wú)需加入屏障,因?yàn)椴皇菑亩阎凶x取引用
int i = ?obj.FieldB ?//無(wú)需加入屏障,因?yàn)椴皇菍?duì)象引用

ZGC中讀屏障的代碼作用:在對(duì)象標(biāo)記和轉(zhuǎn)移過程中,用于確定對(duì)象的引用地址是否滿足條件,并作出相應(yīng)動(dòng)作。

?ZGC并發(fā)處理演示

接下來詳細(xì)介紹ZGC一次垃圾回收周期中地址視圖的切換過程:

初始化:ZGC初始化之后,整個(gè)內(nèi)存空間的地址視圖被設(shè)置為Remapped。程序正常運(yùn)行,在內(nèi)存中分配對(duì)象,滿足一定條件后垃圾回收啟動(dòng),此時(shí)進(jìn)入標(biāo)記階段。
并發(fā)標(biāo)記階段:第一次進(jìn)入標(biāo)記階段時(shí)視圖為M0,如果對(duì)象被GC標(biāo)記線程或者應(yīng)用線程訪問過,那么就將對(duì)象的地址視圖從Remapped調(diào)整為M0。所以,在標(biāo)記階段結(jié)束之后,對(duì)象的地址要么是M0視圖,要么是Remapped。如果對(duì)象的地址是M0視圖,那么說明對(duì)象是活躍的;如果對(duì)象的地址是Remapped視圖,說明對(duì)象是不活躍的。
并發(fā)轉(zhuǎn)移階段:標(biāo)記結(jié)束后就進(jìn)入轉(zhuǎn)移階段,此時(shí)地址視圖再次被設(shè)置為Remapped。如果對(duì)象被GC轉(zhuǎn)移線程或者應(yīng)用線程訪問過,那么就將對(duì)象的地址視圖從M0調(diào)整為Remapped。
其實(shí),在標(biāo)記階段存在兩個(gè)地址視圖M0和M1,上面的過程顯示只用了一個(gè)地址視圖。之所以設(shè)計(jì)成兩個(gè),是為了區(qū)別前一次標(biāo)記和當(dāng)前標(biāo)記。也即,第二次進(jìn)入并發(fā)標(biāo)記階段后,地址視圖調(diào)整為M1,而非M0。

著色指針和讀屏障技術(shù)不僅應(yīng)用在并發(fā)轉(zhuǎn)移階段,還應(yīng)用在并發(fā)標(biāo)記階段:將對(duì)象設(shè)置為已標(biāo)記,傳統(tǒng)的垃圾回收器需要進(jìn)行一次內(nèi)存訪問,并將對(duì)象存活信息放在對(duì)象頭中;而在ZGC中,只需要設(shè)置指針地址的第42~45位即可,并且因?yàn)槭羌拇嫫髟L問,所以速度比訪問內(nèi)存更快。

垃圾處理器總結(jié)

Serial,單線程,Client默認(rèn)新生代處理器,新生代,復(fù)制算法,可與CMS(jdk9之后不行),Serial Old搭配。

ParNew,多線程,Serial的多線程版,新生代,復(fù)制算法,可與CMS,Serial Old(jdk9之后不行)搭配。

Parallel Scavenge,多線程,注重停頓時(shí)間和吞吐量,新生代,復(fù)制算法,可與Parallel Old,Serial Old搭配。

Serial Old,單線程,Client默認(rèn)老年代處理器,Serial的老年版本,CMS的后備方案,老年代,標(biāo)記整理,可與Serial,ParNew,Parallel Scavenge搭配。

Parallel Old,多線程,注重停頓時(shí)間和吞吐量,Parallel Scavenge的老年版本,老年代,標(biāo)記整理,可與Parallel Scavenge搭配。

CMS,多線程,初始標(biāo)記(單),并發(fā)標(biāo)記(并發(fā)),重新標(biāo)記(多線程,但與用戶線程不并發(fā)),并發(fā)清除(并發(fā),失敗使用Serial Old),老年代,標(biāo)記清除,可與Serial,ParNew搭配。

G1,多線程,內(nèi)存分區(qū)(各分區(qū)大小相同,同一分區(qū)邏輯角色相同,都是新生代等,回收以分區(qū)為單位,復(fù)制到另一個(gè)分區(qū)),首先回收垃圾最多的分區(qū),低停頓,使用暫停預(yù)測(cè)模型。新老年代,整體標(biāo)記整理,局部復(fù)制,獨(dú)自工作

分工,G1獨(dú)自工作。Serial,ParNew兩個(gè)新生代,CMS,Serial Old兩個(gè)老年代,可以兩兩搭配。Parallel Scavenge搭配Parallel Old和Serial Old。

總結(jié)

以上是生活随笔為你收集整理的java垃圾收集器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。