JVM_06 垃圾收集器[ 三 ]
截止JDK1.8,一共有7款不同的垃圾收集器。
每一款不同的垃圾收集器都有不同的特點(diǎn),在具體使用的時(shí)候,需要根據(jù)具體的情況選用不同的垃圾收集器
注意:標(biāo)記算法需要維護(hù)一個(gè)空閑列表,復(fù)制和標(biāo)記壓縮算法則使用指針碰撞
根據(jù)不同情況搭配垃圾回收器:
- 如果想要最小化地使用內(nèi)存和并行開銷(串行),選擇Serial GC + Serial Old- 如果想要最大化應(yīng)用程序的吞吐量,選擇parallel GC + Parallel Old (JDK1.8的默認(rèn)選擇)(并行)- 如果想要最小化GC的中斷或停頓時(shí)間,選擇ParNewGC (并行)+ CMS GC(并行并發(fā))一、 評(píng)估GC的性能指標(biāo) 掌握
吞吐量:運(yùn)行用戶代碼的時(shí)間占總運(yùn)行時(shí)間的比例
(總運(yùn)行時(shí)間:程序的運(yùn)行時(shí)間 ? 內(nèi)存回收的時(shí)間)
暫停時(shí)間:執(zhí)行垃圾收集時(shí),程序的工作線程被暫停的時(shí)間
內(nèi)存占用: Java堆區(qū)所占的內(nèi)存大小
垃圾收集開銷:吞吐量的補(bǔ)數(shù),垃圾收集所用時(shí)間與總運(yùn)行時(shí)間的比例。
收集頻率:相對(duì)于應(yīng)用程序的執(zhí)行,收集操作發(fā)生的頻率
(類似于大學(xué)洗衣服,天天洗,每次很快洗完;一周洗,洗很久)
(手機(jī)頻率高,垃圾線程所用時(shí)間短,吞吐量低)
說明:
- 這三者共同構(gòu)成一個(gè)“不可能三角”。三者總體的表現(xiàn)會(huì)隨著技術(shù)進(jìn)步而越來越好。一款優(yōu)秀的收集器通常最多同時(shí)滿足其中的兩項(xiàng)。
- 這三項(xiàng)里,暫停時(shí)間的重要性日益凸顯。因?yàn)殡S著硬件發(fā)展,內(nèi)存占用多些越來越能容忍,硬件性能的提升也有助于降低收集器運(yùn)行時(shí)對(duì)應(yīng)用程序的影響,即提高了吞吐量。而內(nèi)存的擴(kuò)大,對(duì)延遲反而帶來負(fù)面效果。
- 簡(jiǎn)單來說,主要抓住兩點(diǎn):吞吐量、暫停時(shí)間
吞吐量
暫停時(shí)間
- 在設(shè)計(jì)(或使用) GC算法時(shí),我們必須確定我們的目標(biāo): 一個(gè)GC算法只可能針對(duì)兩個(gè)目標(biāo)之一(即只專注于較大吞吐量或最小暫停時(shí)間),或.嘗試找到一個(gè)二者的折衷
- 現(xiàn)在標(biāo)準(zhǔn):在最大吞吐量?jī)?yōu)先的情況下,降低停頓時(shí)間。
二、不同的垃圾回收器概述
垃圾收集器發(fā)展史
7款經(jīng)典的垃圾收集器
- 串行回收器:Serial、Serial Old
- 并行回收器:ParNew、Parallel Scavenge、 Parallel Old
- 并發(fā)回收器:CMS、G1
黃線為GC線程,藍(lán)線為用戶線程
GC發(fā)展過程:
7款經(jīng)典的垃圾收集器與垃圾分代之間的關(guān)系
新生代收集器: Serial、 ParNeW、Parallel Scavenge
老年代收集器: Serial 0ld、 Parallel 0ld、 CMS
整堆收集器: G1(因?yàn)槭褂梅謪^(qū)算法,所以能在新生代和老年代在起作用)
垃圾收集器的組合關(guān)系 超級(jí)重要
- 兩個(gè)收集器間有連線,表明它們可以搭配使用:Serial/Serial 01d、Serial/CMS、 ParNew/Serial 01d、ParNew/CMS、Parallel Scavenge/Serial 01d、Parallel Scavenge/Parallel 0ld、G1;
- 其中Serial 0ld作為CMS 出現(xiàn)"Concurrent Mode Failure"失敗的后備預(yù)案。
- (紅色虛線)由于維護(hù)和兼容性測(cè)試的成本,在JDK8時(shí)將Serial+CMS、ParNew+Serial 01d這兩個(gè)組合聲明為廢棄(JEP 173) ,并在JDK 9中完全取消了這些組合的支持(JEP214),即:移除。(綠色虛線)JDK 14中:棄用Parallel Scavenge和Serial0ld GC組合(JEP366 )(青色虛線)JDK 14中:刪除CMS垃圾回收器 (JEP 363)
(1). -xx:+PrintCommandLineFlags: 查看命令行相關(guān)參數(shù)(包含使用的垃圾收集器)
(2). 使用命令行指令: jinfo 一flag 相關(guān)垃圾回收器參數(shù)進(jìn)程ID
( jinfo -flag UseParallelGC 進(jìn)程id
jinfo -flag UseParallelOldGC 進(jìn)程id )
三、Serial、SerialOld 回收器:串行回收
Serial收集器采用復(fù)制算法、串行回收和"Stop一 the一World"機(jī)制的方式執(zhí)行內(nèi)存回收
Serial 0ld收集器同樣也采用了串行回收 和"Stop the World"機(jī)制,只不過內(nèi)存回收算法使用的是標(biāo)記一壓縮算法
單線程回收:使用一個(gè)cpu或一條線程去完成垃圾收集工作 | 必須暫停其他所有的工作線程
使用 -XX: +UseSerialGC 參數(shù)可以指定年輕代和老年代都使用串行收集器
- 等價(jià)于新生代用Serial GC,且老年代用Serial 0ld GC
控制臺(tái)輸出:
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC
四、ParNew回收器:并行回收(了解)
如果說Serial GC是年輕代中的單線程垃圾收集器,那么ParNew收集器則是Serial收集器的多線程版本
ParNew收集器除了采用并行回收的方式執(zhí)行內(nèi)存回收外,兩款垃圾收集器之間幾乎沒有任何區(qū)別。ParNew收集器在年輕代中同樣也是采用復(fù)制算法、"Stop一the一World"機(jī)制
因?yàn)槌齋erial外,目前只有ParNew GC能與CMS收集器配合工作
在程序中,開發(fā)人員可以通過選項(xiàng)"-XX:+UseParNewGC"手動(dòng)指定使用.ParNew收集器執(zhí)行內(nèi)存回收任務(wù)。它表示年輕代使用并行收集器,不影響老年代
-XX:ParallelGCThreads 限制線程數(shù)量,默認(rèn)開啟和CPU數(shù)據(jù)相同的線程數(shù)。
五、Parallel回收器:吞吐量?jī)?yōu)先在Java8中,默認(rèn)是此垃圾收集器
HotSpot的年輕代中除了擁有ParNew收集器是基于并行回收的以外, Parallel Scavenge收集器同樣也采用了復(fù)制算法、并行回收和"Stop the World"機(jī)制。
那么Parallel收集器的出現(xiàn)是否多此一舉?
- 和ParNew收集器不同,Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量(Throughput),它也被稱為吞吐量?jī)?yōu)先的垃圾收集器。
- 自適應(yīng)調(diào)節(jié)策略也是ParallelScavenge 與ParNew一個(gè)重要區(qū)別。
高吞吐量則可以高效率地利用CPU 時(shí)間,盡快完成程序的運(yùn)算任務(wù),主要適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。因此,常見在服務(wù)器環(huán)境中使用。例如,那些執(zhí)行批量處理、訂單處理、工資支付、科學(xué)計(jì)算的應(yīng)用程序。
Parallel收集器在JDK1.6時(shí)提供了用于執(zhí)行老年代垃圾收集的 Parallel 0ld收集器,用來代替老年代的Serial 0ld收集器
Parallel 0ld收集器采用了標(biāo)記一壓縮算法,但同樣也是基于并行回收和”Stop一the一World"機(jī)制
在程序吞吐量?jī)?yōu)先的應(yīng)用場(chǎng)景中,Parallel 收集器和Parallel 0ld收集器的組合,在Server模式下的內(nèi)存回收性能很不錯(cuò)
參數(shù)配置
六、CMS低延遲(jdk9中被廢棄、jdk14中被移除)
①. 在JDK1.5時(shí)期, HotSpot推出了一款在強(qiáng)交互應(yīng)用中幾乎可認(rèn)為有劃 時(shí)代意義的垃圾收集器: CMS (Concurrent
一Mark 一
Sweep)收集器,這款收集器是HotSpot虛擬機(jī)中第一款真正意義上的并發(fā)收集器,它第一次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程同時(shí)工作。
②. CMS收集器的關(guān)注點(diǎn)是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間。停頓時(shí)
間越短(低延遲)就越適合與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗(yàn)。
③. CMS的垃圾 收集算法采用標(biāo)記一清除算法,并且也 會(huì)" stop一the一world"
④. 不幸的是,CMS 作為老年代的收集器,卻無法與JDK 1.4.0 中已經(jīng)存在的新生代收集器Parallel
Scavenge配合工作,所以在JDK 1. 5中使用CMS來收集老年代的時(shí)候,新生代只能選擇ParNew或者Serial收集器中的一個(gè)
⑤. 在G1出現(xiàn)之前,CMS使用還是非常廣泛的。一直到今天,仍然有很多系統(tǒng)使用CMS GC
①. 初始標(biāo)記(Initial一Mark)僅僅只是標(biāo)記出和GCRoots能直接關(guān)聯(lián)到的對(duì)象,有stw現(xiàn)象
②. 并發(fā)標(biāo)記(Concurrent一Mark)階段:從GC Roots的直接關(guān)聯(lián)對(duì)象開始遍歷整個(gè)對(duì)象圖的過程,這個(gè)過程耗時(shí)較長但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)運(yùn)行
(并發(fā)標(biāo)記階段有三色標(biāo)記,下文有記錄)
③. 重新標(biāo)記(Remark) 階段:有些對(duì)象可能開始是垃圾,在并發(fā)標(biāo)記階段,由于用戶線程的影響,導(dǎo)致不是垃圾了,這里需要重新標(biāo)記的是這部分對(duì)象,這個(gè)階段的停頓時(shí)間通常會(huì)比初始標(biāo)記階段稍長一些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短
④. 并發(fā)清除:此階段清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對(duì)象,釋放內(nèi)存空間。由于不需要移動(dòng)存活對(duì)象,所以這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的,注意:(并發(fā)清除階段會(huì)有浮動(dòng)垃圾的產(chǎn)生)
什么是浮動(dòng)垃圾:
remark過程標(biāo)記活著的對(duì)象,從GCRoot的可達(dá)性判斷對(duì)象活著,但無法標(biāo)記“死亡”的對(duì)象。如果在初始標(biāo)記階段被標(biāo)記為活著,并發(fā)運(yùn)行過程中“死亡”,remark過程無法糾正,因此變?yōu)楦?dòng)垃圾,需等待下次gc的到來。
⑤. 注意:
①. 優(yōu)點(diǎn):并發(fā)收集、低延遲
②. CMS的弊端:
③.區(qū)分兩個(gè)注意事項(xiàng):掌握
-XX:+UseConcMarkSweepGc 手動(dòng)指定使用CMS收集器執(zhí)行內(nèi)存回收任務(wù) (開啟該參數(shù)后會(huì)自動(dòng)將一XX:+UseParNewGc打開。即: ParNew (Young區(qū)用) +CMS (0ld區(qū)用) +Serial 0ld的組合)
其他參數(shù)
①. 其實(shí)三色標(biāo)記就是我們CMS在掃描過程中對(duì)對(duì)象的一種定義。那么具體的定義如下:
- 黑色(black):節(jié)點(diǎn)被遍歷完成,而且子節(jié)點(diǎn)都遍歷完成
- 灰色(gray): 當(dāng)前正在遍歷的節(jié)點(diǎn),而且子節(jié)點(diǎn)還沒有遍歷
- 白色(white):還沒有遍歷到的節(jié)點(diǎn),即灰色節(jié)點(diǎn)的子節(jié)點(diǎn)
②. 根據(jù)三色掃描算法,如果有下面兩種情況發(fā)生,則會(huì)出現(xiàn)漏掃描的場(chǎng)景:
- 把一個(gè)白對(duì)象的引用存到黑對(duì)象的字段里,如果這個(gè)情況發(fā)生,因?yàn)闃?biāo)記為黑色的對(duì)象認(rèn)為是掃描完成的,不會(huì)再對(duì)他進(jìn)行掃描。只能通過灰色的對(duì)象(CMS垃圾收集器)
- 某個(gè)白對(duì)象失去了所有能從灰對(duì)象到達(dá)它的引用路徑(直接或間接)(g1垃圾收集器)
③. 三色過程:如下圖所示,假如說A引入了B,B引用了C,D沒有被任何引用。那么首先我們的CMS首先掃描到了A,發(fā)現(xiàn)A有引用B,那么我們的CMS會(huì)將A標(biāo)記為黑色,B標(biāo)記為灰色,然后這時(shí)候,通過B又找到了C那么這個(gè)時(shí)候發(fā)現(xiàn)C已經(jīng)沒有任何引用了就會(huì)將C標(biāo)記為黑色。但是我們的D到目前為止沒有被任何引用,記住我這里說的條件!!!那么D從始至終都沒有被掃描,此時(shí)就會(huì)一直是白色,對(duì)于白色的對(duì)象來說CMS在執(zhí)行并發(fā)清理的時(shí)候就會(huì)將此類對(duì)象干掉。
④. 但是這里有了一個(gè)問題:如果我們的掃描過程已經(jīng)結(jié)束這一段了,但是此時(shí)此刻我的A突然引用了D類型怎么辦,這樣一來我們的D只要被GC干掉是不是就會(huì)出現(xiàn)問題?也就是說我這里產(chǎn)生了一個(gè)漏標(biāo)的問題。當(dāng)然,我們的JVM開發(fā)人員可不是傻子,這里他們用了一個(gè)操作叫做增量更新和寫屏障來解決這種問題的。
①. 所謂增量更新就是在并發(fā)標(biāo)記過程中,把賦值的這種新增的引用,做一個(gè)集合存起來。 在重新標(biāo)記的時(shí)候會(huì)找到集合里面的引用然后重新去掃描,再把源頭標(biāo)記為灰色。這就是我們的增量更新
②. 當(dāng)然,在把我們新增的引用放到集合的時(shí)候,會(huì)實(shí)現(xiàn)一種寫屏障的方式。在對(duì)象前后通過一個(gè)dirty card queue將引用信息,存在card中,這個(gè)dirty card queue會(huì)放在cardtable中,而cardtable是記憶集的具體實(shí)現(xiàn),最終這個(gè)引用就會(huì)放在記憶集中的 (寫屏障我們可以理解為在賦值操作的前面加一個(gè)方法,賦值的后面做一些操作,也可以理解為AOP。具體的C++實(shí)現(xiàn)代碼如下圖:)
①. 在剛剛我們?cè)僬f寫屏障的時(shí)候提到了卡表,那么我們現(xiàn)在就來說說卡表是干什么用的。但是在說記憶集與卡表之前,我們要先知道what is 跨代引用~
②. 跨代引用:所謂跨代引用就是老年代的對(duì)象引用了新生代的對(duì)象,或者新生代的對(duì)象引用了老年代的對(duì)象。那對(duì)于這種情況我們的GC在進(jìn)行掃描的時(shí)候不可能直接把我們的整個(gè)堆都掃描完,那這樣效率也太低了。所以這時(shí)候就需要開辟了一小塊空間,維護(hù)這種引用,而不必讓GC掃描整個(gè)堆區(qū)域。
③. 記憶集: 記憶集也叫rememberSet,垃圾收集器在新生代中建立了記憶集這樣的數(shù)據(jù)結(jié)構(gòu),用來避免把整個(gè)老年代加入到GC ROOTS的掃描范圍中。對(duì)于記憶集來說,我們可以理解為他是一個(gè)抽象類,那么具體實(shí)現(xiàn)它的方法將由子類去完成。這里我們簡(jiǎn)單列舉一下實(shí)現(xiàn)記憶集的三種方式:
④. 卡表: 卡表(Car Table)是一種對(duì)記憶集的具體實(shí)現(xiàn)。主要定義了記憶集的記錄精度、與堆內(nèi)存的映射關(guān)系等??ū碇械拿恳粋€(gè)元素都對(duì)應(yīng)著一塊特定大小的內(nèi)存塊,這個(gè)內(nèi)存塊我們稱之為卡頁(card page),當(dāng)存在跨帶引用的時(shí)候,它會(huì)將卡頁標(biāo)記為dirty。那么JVM對(duì)于卡頁的維護(hù)也是通過寫屏障的方式,這也就是為什么剛剛我們跟進(jìn)寫屏障操作到最后會(huì)發(fā)現(xiàn)它會(huì)對(duì)卡表進(jìn)行一系列的操作。
-
①. satb 算法認(rèn)為開始標(biāo)記的都認(rèn)為是活的對(duì)象,如上圖所示,引用B到D 的引用改為B到C時(shí),通過write barrier寫屏障技術(shù),會(huì)把B到D 的引用推到gc 遍歷執(zhí)行的堆棧上,保證還可以遍歷到D對(duì)象,相對(duì)于d來說,引用從B–>A,SATB是從源入手解決的,即上面說的第2種情況, 這也能理解為啥叫satb 了,即認(rèn)為開始時(shí)所有能遍歷到的對(duì)象都是需要標(biāo)記的,即都認(rèn)為是活的。如果我吧b = null,那么d 久是垃圾了, satb算法也還是會(huì)把D最終標(biāo)記為黑色,導(dǎo)致D 在本輪gc 不能回收,成了浮動(dòng)垃圾
-
②. Incremental Update
算法判斷如果一個(gè)白色的對(duì)象由一個(gè)黑色的對(duì)象引用,即目的,如上圖,D的引用由B–>A,A是目的地址,所以cms 的Incremental Update算法是從目標(biāo)入手解決的,這是和SATB的第一個(gè)區(qū)別,發(fā)現(xiàn)這種情況時(shí),也是通過write barrier寫屏障技術(shù),把黑色的對(duì)象重新標(biāo)記為灰色,讓collector 重新來掃描,活著通過mod-union table 來標(biāo)記,cms 就是這樣實(shí)現(xiàn)的,這是第二個(gè)區(qū)別,做法不一樣,也是上面講的防止第一種情況發(fā)生
七、G1回收器 掌握(jdk9默認(rèn)垃圾收集器)
-
①.G1是一個(gè)并行回收器,它把堆內(nèi)存分割為很多不相關(guān)的區(qū)域(region物理上不連續(xù)),把堆分為2048個(gè)區(qū)域,每一個(gè)region的大小是1 -32M不等,必須是2的整數(shù)次冪。使用不同的region可以來表示Eden、幸存者0區(qū)、幸存者1區(qū)、老年代等
-
②. 每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region (每次回收完以后都有一個(gè)空閑的region,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表)
-
③. 由于這種方式的
①. 并行和并發(fā)
- 并行性: G1在回收期間,可以有多個(gè)Gc線程同時(shí)工作,有效利用多核計(jì)算能力。此時(shí)用戶線程STW
- 并發(fā)性: G1擁有與應(yīng)用程序交替執(zhí)行的能力,部分工作可以和應(yīng)用程序同時(shí)執(zhí)行,因此,一般來說,不會(huì)在整個(gè)回收階段發(fā)生完全阻塞應(yīng)用程序的情況
②. 分代收集
- 從分代上看,G1依然屬于分代型垃圾回收器,他會(huì)區(qū)分年輕代和老年代,年輕代依然有Eden區(qū)和servivor區(qū),但從堆結(jié)構(gòu)上看,他不要求整個(gè)Eden區(qū)、你年輕代或者老年代都是連續(xù)的,也不再堅(jiān)持固定大小和數(shù)量
- 將堆空間分為若干個(gè)區(qū)域(Region),這些區(qū)域中包含了邏輯上的年輕代和老年代,H用來存儲(chǔ)大對(duì)象
- 和之前的各類回收器不同,它同時(shí)兼顧年輕代和老年代0,對(duì)比其他回收器,或者工作在年輕代,或者工作在老年代
③. 空間整合
(G1將內(nèi)存劃分為一個(gè)個(gè)的region。 內(nèi)存的回收是以region作為基本單位的.Region之間是復(fù)制算法,但整體上實(shí)際可看作是標(biāo)記一壓縮(Mark一Compact)算法,兩種算法都可以避免內(nèi)存碎片。這種特性有利于程序長時(shí)間運(yùn)行,分配大對(duì)象時(shí)不會(huì)因?yàn)闊o法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC。尤其是當(dāng)Java堆非常大的時(shí)候,G1的優(yōu)勢(shì)更加明顯)
④. 可預(yù)測(cè)的停頓時(shí)間模型(即:軟實(shí)時(shí)soft real一time)
(可以通過參數(shù)-XX:MaxGCPauseMillis進(jìn)行設(shè)置)
-
①. -XX:+UseG1GC 手動(dòng)指定使用G1收集器執(zhí)行內(nèi)存回收任務(wù)
-
②. -XX:G1HeapRegionSize 設(shè)置每個(gè)Region的大小。值是2的冪,范圍是1MB到32MB之間,目標(biāo)是根據(jù)最小的Java堆大小劃分出約2048個(gè)區(qū)域。默認(rèn)是堆內(nèi)存的1/2000
-
③. -XX:MaxGCPauseMillis 設(shè)置期望達(dá)到的最大Gc停頓時(shí)間指標(biāo)(JVM會(huì)盡力實(shí)現(xiàn),但不保證達(dá)到)。默認(rèn)值是200ms(如果這個(gè)值設(shè)置很小,如20ms,那么它收集的region會(huì)少,這樣長時(shí)間后,堆內(nèi)存會(huì)滿。產(chǎn)生FullGC,FullGC會(huì)出現(xiàn)STW,反而影響用戶體驗(yàn))
-
④. -XX:ParallelGCThread 設(shè)置stw.工作線程數(shù)的值。最多設(shè)置為8
-
⑤. -XX:ConcGCThreads設(shè)置并發(fā)標(biāo)記的線程數(shù)。將n設(shè)置為并行垃圾回收線程數(shù)(ParallelGCThreads)的1/4左右
-
⑥. -XX:InitiatingHeapOccupancyPercent 設(shè)置觸發(fā)并發(fā)GC周期的Java堆占用率閾值。超過此值,就觸發(fā)GC。默認(rèn)值是45
- G1的調(diào)優(yōu)
-
①. 面向服務(wù)端應(yīng)用,針對(duì)具有大內(nèi)存,多處理器的機(jī)器
-
②. 最主要的應(yīng)用時(shí)需要低GC延遲,并且有大堆的應(yīng)用程序提供解決方案
-
①. 使用G1收集器時(shí),它將整個(gè)Java堆劃分成約2048個(gè)大小相同的獨(dú)立Region塊,每個(gè)Region塊大小根據(jù)堆空間的實(shí)際大小而定,整體被控制在1MB到32MB之間,且為2的N次冪,即1MB,2MB, 4MB, 8MB, 1 6MB, 32MB。可以通過-XX:G1HeapRegionSize設(shè)定。所有的Region大小相同,且在JVM生命周期內(nèi)不會(huì)被改變
-
②. 一個(gè)region 有可能屬于Eden, Survivor 或者0ld/Tenured 內(nèi)存區(qū)域。但是一個(gè)region只可能屬于一個(gè)角色。圖中的E表示該region屬于Eden內(nèi)存區(qū)域,s表示屬于Survivor內(nèi)存區(qū)域,0表示屬于0ld內(nèi)存區(qū)域。圖中空白的表示未使用的內(nèi)存空間
-
③. 垃圾收集器還增加了一種新的內(nèi)存區(qū)域,叫做Humongous內(nèi)存區(qū)域,如圖中的H塊。主要用于存儲(chǔ)大對(duì)象,如果超過0.5個(gè)region-Size,就放到H (對(duì)于堆中的大對(duì)象,默認(rèn)直接會(huì)被分配到老年代,但是如果它是一個(gè)短期存在的大對(duì)象,就會(huì)對(duì)垃圾收集器造成負(fù)面影響。為了解決這個(gè)問題,G1劃分了一個(gè)Humongous區(qū),它用來專門存放大對(duì)象。如果一個(gè)H區(qū)裝不下一個(gè)大對(duì)象,那么G1會(huì)尋找連續(xù)的H區(qū)來存儲(chǔ)。為了能找到連續(xù)的H區(qū),有時(shí)候不得不啟動(dòng)FullGC。G1的大多數(shù)行為都把H區(qū)作為老年代的一部分來看待)
-
①. 問題:一個(gè)Region不可能是孤立的,一個(gè)Region中的對(duì)象可能被其他對(duì)象引用,如新生代中引用了老年代,這個(gè)時(shí)候垃圾回收時(shí),會(huì)去掃描老年代,會(huì)出現(xiàn)STW
-
②. 解決:無論是G1還是分代收集器,JVM都是使用Remembered Set來避免全局掃描。每個(gè)Region都有 一個(gè)對(duì)應(yīng)的Remembered Set;[下面過程需要掌握]
- 每次Reference類型數(shù)據(jù)寫操作時(shí),都會(huì)產(chǎn)生一個(gè)Write Barrier
- 然后檢查將要寫入的引用指向的對(duì)象是否和該Reference類型數(shù)據(jù)在不同的Region (其他收集器:檢查老年代對(duì)象是否引用了新生代對(duì)象)
- 如果不同,通過CardTable把相關(guān)引用信息記錄到引用指向?qū)ο蟮乃赗egion對(duì)應(yīng)的Remembered Set中;
- 當(dāng)進(jìn)行垃圾收集時(shí),在GC根節(jié)點(diǎn)的枚舉范圍加入Remembered Set;就可以保證不進(jìn)行全局掃描,也不會(huì)有遺漏
G1回收器垃圾回收過程
年輕代GC
-
①. 根掃描:一定要考慮remembered Set,看是否有老年代中的對(duì)象引用了新生代對(duì)象
-
②.更新RSet:處理dirty card queue(見備注)中的card,更新RSet。 此階段完成后,RSet可以準(zhǔn)確的反映老年代對(duì)所在的內(nèi)存分段中對(duì)象的引用 (dirty card queue:對(duì)于應(yīng)用程序的引用賦值語句object.field=object,JVM會(huì)在之前和之后執(zhí)行特殊的操作以在dirty card queue中入隊(duì)一個(gè)保存了對(duì)象引用信息的card。在年輕代回收的時(shí)候,G1會(huì)對(duì)Dirty CardQueue中所有的card進(jìn)行處理,以更新RSet,保證RSet實(shí)時(shí)準(zhǔn)確的反映引用關(guān)系。那為什么不在引用賦值語句處直接更新RSet呢?這是為了性能的需要,RSet的處理需要線程同步,開銷會(huì)很大,使用隊(duì)列性能會(huì)好很多)
-
③. 處理RSet:識(shí)別被老年代對(duì)象指向的Eden中的對(duì)象,這些被指向的Eden中的對(duì)象被認(rèn)為是存活的對(duì)象
-
④. 復(fù)制對(duì)象:復(fù)制算法 (此階段,對(duì)象樹被遍歷,Eden區(qū)
內(nèi)存段中存活的對(duì)象會(huì)被復(fù)制到Survivor區(qū)中空的內(nèi)存分段,Survivor區(qū)內(nèi)存段中存活的對(duì)象如果年齡未達(dá)閾值,年齡會(huì)加1,達(dá)到閥值會(huì)被會(huì)被復(fù)制到01d區(qū)中空的內(nèi)存分段。如果Survivor空間不夠,Eden空間的部分?jǐn)?shù)據(jù)會(huì)直接晉升到老年代空間) -
⑤. 處理引用: 處理Soft,Weak, Phantom, Final, JNI Weak等引用。最終Eden空間的數(shù)據(jù)為空,GC停止工作,而目標(biāo)內(nèi)存中的對(duì)象都是連續(xù)存儲(chǔ)的,沒有碎片,所以復(fù)制過程可以達(dá)到內(nèi)存整理的效果,減少碎片
-
①. 初始標(biāo)記階段:標(biāo)記從根節(jié)點(diǎn)直接可達(dá)的對(duì)象。這個(gè)階段是STW的,并且會(huì)觸發(fā)一次年輕代GC
-
②. 根區(qū)域掃描(Root Region Scanning):G1GC掃描Survivor區(qū)直接可達(dá)的老年代區(qū)域?qū)ο?#xff0c;并標(biāo)記被引用的對(duì)象。這一過程必須在young GC之前完成(YoungGC時(shí),會(huì)動(dòng)Survivor區(qū),所以這一過程必須在young GC之前完成)
-
③. 并發(fā)標(biāo)記(Concurrent Marking): 在整個(gè)堆中進(jìn)行并發(fā)標(biāo)記(和應(yīng)用程序并發(fā)執(zhí)行),此過程可能被young GC中斷。在并發(fā)標(biāo)記階段,若發(fā)現(xiàn)區(qū)域?qū)ο笾械乃袑?duì)象都是垃圾,那這個(gè)區(qū)域會(huì)被立即回收。同時(shí),并發(fā)標(biāo)記過程中,會(huì)計(jì)算每個(gè)區(qū)域的對(duì)象活性(區(qū)域中存活對(duì)象的比例)。
-
④. 再次標(biāo)記(Remark):由
于應(yīng)用程序持續(xù)進(jìn)行,需要修正上一次的標(biāo)記結(jié)果。是STW的。G1中采用了比CMS更快的初始快照算法:snapshot一at一the一beginning(SATB) (在CMS中有詳細(xì)講解) -
⑤.獨(dú)占清理(cleanup,STW):計(jì)算各個(gè)區(qū)域的存活對(duì)象和GC回收比例,并進(jìn)行排序,識(shí)別可以混合回收的區(qū)域。為下階段做鋪墊。是STW的。(這個(gè)階段并不會(huì)實(shí)際上去做垃圾的收集)
-
⑥. 并發(fā)清理階段:識(shí)別并清理完全空閑的區(qū)域
①. 當(dāng)越來越多的對(duì)象晉升到老年代oldregion時(shí),為了避免堆內(nèi)存被耗盡,虛擬機(jī)會(huì)觸發(fā)一個(gè)混合的垃圾收集器,即Mixed GC, 該算法并不是一個(gè)0ldGC,除了回收整個(gè)Young Region,還會(huì)回收一部分的0ldRegion。這里需要注意:是一部分老年代, 而不是全部老年代??梢赃x擇哪些0ldRegion進(jìn)行收集,從而可以對(duì)垃圾回收的耗時(shí)時(shí)間進(jìn)行控制。也要注意的是Mixed GC并不是Fu1l GC
②. 其他說明:
③. Full GC
-
①. 堆內(nèi)存過小,當(dāng)G1在復(fù)制存活對(duì)象的時(shí)候沒有空的內(nèi)存分段可用,則會(huì)回退到full gc,這種情況可以通過增大內(nèi)存解決
-
②. 暫停時(shí)間-XX:MaxGCPauseMillis設(shè)置短,回收頻繁。由于用戶線程和GC線程一起執(zhí)行,可能用戶線程產(chǎn)生的垃圾大于GC線程回收的垃圾,會(huì)導(dǎo)致內(nèi)存不足,觸發(fā)Full
gc
④. 優(yōu)化建議
-
①. 年輕代發(fā)送GC頻率高,避免使用-Xmn或-XX:NewRatio,讓JVM自己設(shè)置
-
②. 暫停時(shí)間目標(biāo)不要太過嚴(yán)苛
八、 詳解-XX:+PrintGCDetails
①. Minor GC
②. Full GC
③. 舉例補(bǔ)充
/*** 在jdk7 和 jdk8中分別執(zhí)行* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC*/ public class GCLogTest1 {private static final int _1MB = 1024 * 1024;public static void testAllocation() {byte[] allocation1, allocation2, allocation3, allocation4;allocation1 = new byte[2 * _1MB];allocation2 = new byte[2 * _1MB];allocation3 = new byte[2 * _1MB];allocation4 = new byte[4 * _1MB];}public static void main(String[] agrs) {testAllocation();} }九、其他垃圾回收器概述
- ①. Open JDK12的Shenandoah GC
- 暫時(shí)時(shí)間短、吞吐量下降
- red hot 團(tuán)隊(duì)
- ②. 革命性的ZGC(jdk11-oracle)
( 在盡可能對(duì)吞吐量影響不大的前提下.實(shí)現(xiàn)在任意堆內(nèi)存大小都可以把垃圾收集的停頓時(shí)間控制在10毫秒之內(nèi))
總結(jié)
以上是生活随笔為你收集整理的JVM_06 垃圾收集器[ 三 ]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一句话证明你是产品经理
- 下一篇: JVM_07 Class文件结构