GC詳解,看完這篇同事小勇都驚呆了
前言
在一個風(fēng)和日麗的中午,和同事小勇一起走在公司樓下的小公園里面,看到很多的小姐姐,心想什么時候能夠和這些小姐姐一起討論人生呀,美滋滋,嘿嘿嘿。
- 收起你的哈喇子好不好,小勇總是在這個時候發(fā)出聲音,挺讓人喜(fu)歡(ck)的。
- 小勇:小農(nóng),現(xiàn)在不是推崇垃圾分類嗎,你說到底什么是垃圾?小勇總是在我和他散步的時候,問這么讓人深思的問題!
- 我:什么是垃圾啊,你不就是垃圾嗎?
- 小勇:去你大爺?shù)?,正?jīng)的。
- 我:小勇啊,答應(yīng)我以后散步的時候我們討論點(diǎn)輕松點(diǎn)的問題好嘛?垃圾是啥,垃圾就是沒有引用的對象就是垃圾啊
- 小勇:。。。。,我們還是去午休吧
- 我:別啊,都講到這里了,給你普及一下,你難道不想以后你的簡歷上出現(xiàn)——熟悉GC常用算法,熟悉常見垃圾收集器,具有實際JVM調(diào)優(yōu)實戰(zhàn)經(jīng)驗嗎?保證讓你豁然開朗,等你以后去面試的時候,給面試官講這些保證妥妥的。
- 小勇:你這么說我倒是有點(diǎn)興趣,但是如果講不明白,那你就浪費(fèi)了我時間了,晚飯就你請吧。我是沒問題,但是我的三個粉絲不會答應(yīng)你的小勇:你沒問題就行了,請開始你的表演吧~
什么是垃圾
什么是垃圾,就是沒有任何引用指向的一個對象或者多個對象(循環(huán)引用),但是他們卻依然占據(jù)著內(nèi)存空間。
GC是一種自動的存儲管理機(jī)制。當(dāng)一些被占用的內(nèi)存不再需要時,就應(yīng)該予以釋放。這種存儲資源管理,稱為垃圾回收。
就像我們的衣柜一樣,我們里面可能存放這很多衣服,有可能幾個月或者幾年都不會穿過一次,但是這些我們不穿的衣服一直霸占著我們的衣柜(內(nèi)存),我們把這些不會穿的衣服扔掉的或者捐贈出去,這樣我們就可以放更多可以穿的衣服,這個就類似于“垃圾回收”。
在GC里面,只分為可回收和不可回收,如下圖所示:
1.1 Java 和 C++ 垃圾回收的區(qū)別
Java是你只管扔垃圾就可以,Java會自動幫你處理,而C++要手動處理,但是容易造成一個問題就是忘記回收或者回收多次
- java
- GC處理垃圾
- 開發(fā)效率高,執(zhí)行效率低
- C++
- 手工處理垃圾
- 忘記回收,會導(dǎo)致內(nèi)存泄漏
- 回收多次,非法訪問
- 開發(fā)效率,執(zhí)行效率高
怎么找垃圾?
上面我們知道了什么是垃圾,那么我們?nèi)绾稳フ业嚼?
在堆里面存放這Java中幾乎所有的對象實例,垃圾收集器在對堆進(jìn)行回收前,首先要做的事情就是確定這些對象哪些還 “存活”,哪些是需要進(jìn)行回收的(即不再被引用的對象)
找到垃圾有兩種算法
- reference count (引用計數(shù)算法)
- Root Searching (根可達(dá)算法)
1. 引用計數(shù)法
會給對象中添加一個引用計數(shù)器,每當(dāng)有一個地方引用它的時候,計數(shù)器的值就 +1 ,當(dāng)引用失效時,計數(shù)器值就 -1 ,計數(shù)器的值為 0 的對象不可能在被使用,這個時候就可以判定這個對象是垃圾。
當(dāng)圖中的數(shù)值變成0時,這個時候使用引用計數(shù)算法就可以判定它是垃圾了,但是引用計數(shù)法不能解決一個問題,就是當(dāng)對象是循環(huán)引用的時候,計數(shù)器值都不為0,這個時候引用計數(shù)器無法通知GC收集器來回收他們,如下圖所示:
這個時候就需要使用到我們的根可達(dá)算法
2. 根可達(dá)算法
根可達(dá)算法的意思是說從根上開始搜索,當(dāng)一個程序啟動后,馬上需要的那些個對象就叫做根對象,所謂的根可達(dá)算法就是首先找到根對象,然后跟著這根線一直往外找到那些有用的,例如我們Java程序 main() 方法運(yùn)行,一個main() 方法會啟動一個線程。
線程棧變量: 線程里面會有線程棧和main棧幀,從這個main() 里面開始的這些對象都是我們的根對象。
靜態(tài)變量: 一個class 它有一個靜態(tài)的變量,load到內(nèi)存之后馬上就得對靜態(tài)變量進(jìn)行初始化,所以靜態(tài)變量到的對象這個叫做根對象。
常量池: 如果你這個class會用到其他的class的那些個類的對象,這些就是根對象。
JNI: 如果我們調(diào)用了 C和C++ 寫的那些本地方法所用到的那些個類或者對象
圖中的 object5 和object6 雖然他們之間互相引用了,但是從根找不到它,所以就是垃圾,而object8沒有任何引用自然而然也是垃圾,其他的Object對象都有可以從根找到的,所以是有用的,不會被垃圾回收掉。
3. 區(qū)別
如何清理垃圾
我們找到對應(yīng)的垃圾之后,我們?nèi)绻デ謇砝?GC常用的算法有三種:
- Mark-Sweep(標(biāo)記清除)
- Copying(拷貝)
- Mark-Compact(標(biāo)記壓縮)
1. 標(biāo)記 - 清除算法
就和它的名字一樣 ,算法分為 “標(biāo)記” 和 “清除” 兩個階段,首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象,這個是最基礎(chǔ)的收集算法,為什么說它是最基礎(chǔ)的,因為后續(xù)的收集器都是基礎(chǔ)這種思路并對其不足進(jìn)行改進(jìn)而得到的。
標(biāo)記清除算法它有自己的小問題,大家可以看到上面這張圖,我們從GC的根找到那些不可回收的,綠色是不可回收的,紫色是可以回收的,我們把它回收之后就變成空閑的了,這種算法相對比較簡單,在存活對象比較多的情況下效率比較高,它需要經(jīng)歷兩次掃描,第一遍掃描是找到那些有用的,第二遍掃描是把那些沒用的找出來清理掉,這里會有兩個問題:一個是效率問題,標(biāo)記和清除兩個過程的效率都不高,另一個是空間問題,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的空間碎片,如果空間碎片太多會導(dǎo)致以后的程序在運(yùn)行過程中需要分配較大對象的時候,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。
2. 復(fù)制算法
為了解決效率的問題,所以有了復(fù)制(Copying)算法的出現(xiàn),它將可用的內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象賦值到另一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉,這樣使得每次都對整個半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況,只需要移動堆頂?shù)闹羔槪@種適用于存活對象較少的情況,所以比較適合eden區(qū),只掃描一次,效率提高了沒有碎片,但是會造成空間的浪費(fèi),將內(nèi)存縮小為原來的一半,未免太高了一點(diǎn),而且移動復(fù)制對象,需要調(diào)整對象的引用
3. 標(biāo)記 壓縮算法
標(biāo)記壓縮就是把所有的東西整理的過程,清理的過程同時壓縮到頭上去?;厥罩埃杏玫耐懊孀?,將剩下的清理出來,但是標(biāo)記壓縮算法依然有它的問題,我們都是通過GC Roots 找到那些不可回收的對象,然后把不可回收的往前挪,這個時候我們需要掃描兩次而且需要移動對象,第一遍掃描出有用的對象,第二遍進(jìn)行移動,而且移動如果是多線程還需要進(jìn)行同步,所以這個效率會低很多,但是它不會產(chǎn)生碎片,分配對象也不會產(chǎn)生內(nèi)存減半。
4. 總結(jié)
- Mark-Sweep(標(biāo)記清除): 標(biāo)記為垃圾之后就給清理掉,別的空間還是固定的,效率還可以,就是容易產(chǎn)生碎片
- Copying(拷貝): 將內(nèi)存一分為二,只使用一半,如果垃圾太多了,拷貝有用的到另外一邊,剩下的清理就直接整個內(nèi)存進(jìn)行清理,效率比較高
- Mark-Compact(標(biāo)記壓縮): 將所有的對象湊在一起,把垃圾全部清理走,接下來剩下的這個空間還是連續(xù)的,在里面分配任何內(nèi)容的時候直接往里面分配就行了
堆內(nèi)存邏輯分區(qū)
JVM中的Hot Spot 用的是分代算法
新生代分為:eden、survivor
eden(伊甸): 默認(rèn)比例8:是我們剛剛新 new出來對象之后往里扔的那塊區(qū)域survivor: 默認(rèn)比例是1:是回收一次之后跑到這個區(qū)域,這里面由于裝的對象不同,所以采取的算法也不同
由于新生代存活對象特別少,死去對象特別多所以使用的算法是 復(fù)制算法
old 老年代:tenured(終身)老年代活著的對象特別多適用于:標(biāo)記清除和標(biāo)記壓縮算法
一個對象從出生到消亡
一個對象產(chǎn)生之后首先進(jìn)行棧上分配,棧上如果分配不下會進(jìn)入伊甸區(qū),伊甸區(qū)經(jīng)過一次垃圾回收之后進(jìn)入surivivor區(qū),survivor區(qū)在經(jīng)過一次垃圾回收之后又進(jìn)入另外一個survivor,與此同時伊甸區(qū)的某些對象也跟著進(jìn)入另外一個survivot,什么時候年齡夠了就會進(jìn)入old區(qū),這是整個對象的一個邏輯上的移動過程。
那什么時候會在棧上分配,什么時候會在伊甸區(qū)分配?
1 棧上分配
棧上分配:
- 線程私有小對象:小對象、線程私有的
- 無逃逸:就在某一段代碼中使用,除了這段代碼就沒有人認(rèn)識它了
- 支持標(biāo)量替換:意思是用普通的屬性、把普通的類型代替對象就叫標(biāo)量替換
棧上分配會比在堆上分配快一點(diǎn),如果在棧上分配不下,會優(yōu)先進(jìn)行本地分配,也就是 線程本地分配TLAB(Thread local Allocation Buffer): 在伊甸區(qū)很多線程都會往里面分配對象,但是分配對象的時候我們一定會進(jìn)行空間的征用,誰搶到算誰的,多線程的同步,效率就會降低,所以設(shè)計了TLAB機(jī)制
- 占用eden,默認(rèn)為1%,在伊甸區(qū)取用百分之一的空間,這塊空間叫做線程獨(dú)有,分配對象的時候首先往線程獨(dú)有的這塊空間進(jìn)行分配
- 多線程的時候不用競爭eden就可以申請空間,提高效率
2 老年代
對象什么時候進(jìn)入老年代?
回收了多少次進(jìn)入老年代?
- 超過 XX:MaxTenuringThreshold指定次數(shù)(YGC)
- Parallel Scavenge 15次進(jìn)入老年代
- CMS 6次進(jìn)入老年代
- G1 15次進(jìn)入老年代
網(wǎng)上有說可以次數(shù)往上調(diào)大,這個是不可能的
動態(tài)年齡判斷
為了能夠適用不同程序的內(nèi)存狀況,虛擬機(jī)并不是永遠(yuǎn)的要求對象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代,如果在Surivivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代,無需等到MaxTenuringThreshold中要求的年齡。
兩個Survivor之間拷貝來拷貝去只要超過百分之50的時候把年齡最大的直接放入到old區(qū),也就是不一定非得到15歲。
在s1里面有這么多對象拷貝到了s2里面超過百分之50的話,s1里面在加上伊甸區(qū)里面,整個一個對象一下子拷貝到s2里面,經(jīng)過一次垃圾回收,過去之后,這個時候整個加起來對象已經(jīng)超過s2的一半了,這里面年齡最大的一些對象直接進(jìn)入老年區(qū),這個就叫做動態(tài)年輕判斷
大對象直接進(jìn)入老年代 ,所謂的大對象是指,需要連續(xù)大量內(nèi)存空間的Java對象,最典型的大對象就是那種很長的字符串以及數(shù)組,經(jīng)常出現(xiàn)大對象容易導(dǎo)致內(nèi)存還有不少空間的時候就提前觸發(fā)了垃圾收集來獲得足夠的連續(xù)內(nèi)存空間
start 先是new一個對象,然后在棧上進(jìn)行分配,如果在棧上能夠分配,就分配到棧上,棧直接彈出,彈出結(jié)束,如果在棧上分配不下,判斷對象是否為大對象,如果是大對象,直接進(jìn)入老年代,F(xiàn)GC后結(jié)束如果不是,進(jìn)入線程本地分配(TLAB),不管怎么樣都會到伊甸區(qū)進(jìn)行GC清除,如果清除完畢,直接結(jié)束,如果沒有清除完畢,進(jìn)入S1,S1繼續(xù)GC清除,如果年齡到了進(jìn)入old區(qū),如果年齡不夠進(jìn)入S2,然后S2再繼續(xù)GC的清除,要么年齡到了,要么動態(tài)年齡達(dá)到
MinorGC/YGC: 年輕代空間耗盡時觸發(fā)MajorGC/FullGC: 在老年代無法繼續(xù)分配空間時觸發(fā),新生代老年代同時進(jìn)行回收
常見的垃圾回收器
新生代收集器: Serial、ParNew、Parallel Scavenge
老年代收集器: Serial Old、CMS、Parallel Old
新生代和老年代收集器: G1、ZGC、Shenandoah
每種垃圾回收器之間不是獨(dú)立操作的,下圖表示垃圾回收器之間有連線表示,可以協(xié)作使用:
新生代垃圾收集器
1. Serial收集器
Serial 收集器是最基礎(chǔ)、歷史最悠久的收集器,是一個單線程工作的收集器,它的“單線程”的意義不是說他只會使用一個處理器或者一條收集線程去完成垃圾收集的工作,更重要的是強(qiáng)調(diào)在它進(jìn)行垃圾收集的時候,會暫停其他所有工作線程,直到它收集結(jié)束
根據(jù)上圖中我們可以知道,當(dāng)Serial收集器運(yùn)行的時候,會暫停所有線程,“Stop The World” ,等到GC完成后,應(yīng)用線程繼續(xù)執(zhí)行,就類似于 你有三個女朋友,他們同時讓你陪他們?nèi)ス浣?,你只能陪完其中一個才能去陪另外一個,陪其中一個的時候,其他女朋友就要等待,但是垃圾收集這項工作要比這種情況要復(fù)雜的多!
優(yōu)勢: 因為使用的是單線程的方式,所以對于單個CPU來說,是其他類型收集器中效率最高的一個
缺點(diǎn): 在用戶不可知、不可控的情況下,暫停所有線程,風(fēng)險性和體驗感不好,讓人比較難接受
使用命令:可以開啟Serial 作為新生代收集器
- -XX:+UserSerialGC #選擇Serial作為新生代垃圾收集器
2. ParNew收集器
ParNew收集器實質(zhì)上是Serial收集器的多線程并行版本,除了同時使用多條線程進(jìn)行垃圾收集器之外,其余的比如Serial收集器可用的控制參數(shù)、收集算法、Stop The Wrold 、對象分配規(guī)則等等都和Serial收集器完全一樣,在多核機(jī)器上,默認(rèn)開啟的手機(jī)線程數(shù)和CPU數(shù)量一樣,但是可以通過參數(shù)進(jìn)行修改
- -XX:ParallelGCThreads #設(shè)置JVM垃圾收集的線程數(shù)
ParNew收集器除了支持多線程并行收集之外,其他與Serial收集器相比并沒有太多創(chuàng)新之處,但它 卻是不少運(yùn)行在服務(wù)端模式下的HotSpot虛擬機(jī),尤其是JDK 7之前的遺留系統(tǒng)中首選的新生代收集 器,其中有一個與功能、性能無關(guān)但其實很重要的原因是:除了Serial收集器外,目前只有它能與CMS 收集器配合工作。
優(yōu)點(diǎn):隨著CPU的有效利用,對于GC時系統(tǒng)資源的有效利用有好處缺點(diǎn):同Serial一樣的毛病使用場景:ParNew是許多運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器,因為CMS只能與Serial 或者 ParNew 配合使用,在如今的多核環(huán)境下,首選的是多線程并行的ParNew,ParNew收集器是激活CMS后 (使用-XX:+UseConcMarkSweepGC選項)的默認(rèn)新生代收集器,也可以使用 -XX:+/-UseParNewGC選項來強(qiáng)制指定或者禁用它
3. Parallel Scavenge收集器
Parallel Scavenge收集器也是一款新生代的收集器,它同樣是基于標(biāo)記-復(fù)制算法那實現(xiàn)的收集器,也是能夠并行收集器的多線程收集器,Parallel Scavenge收集器關(guān)注點(diǎn)與其他收集器的不用處在于,CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標(biāo)則是一個可控制的吞吐量,所謂的吞吐量就是處理器用于運(yùn)行用戶代碼的時間與處理器總消耗的比值,如下圖所示:
如果說虛擬機(jī)完成某個任務(wù),用戶代碼加上垃圾收集總共耗費(fèi)了100分鐘,其中垃圾收集花掉1分鐘,那么吞吐量就是99%。停頓時間越短就越適合需要與用戶交互或者需要保證服務(wù)響應(yīng)質(zhì)量的程序,良好的響應(yīng)速度能提升用戶體驗。
垃圾收集器每100秒收集一次,每次停頓10秒,和垃圾收集器每50秒收集一次,每次停頓時間7秒,雖然后者停頓時間變短了,但是總體吞吐量變低了,CPU總體利用率變低了。
可以通過 -XX:MaxGCPauseMillis來設(shè)置收集器盡可能在多長時間內(nèi)完成內(nèi)存回收,可以通過 -XX:GCTimeRatio來精確控制吞吐量。
如下是 Parallel 收集器和 Parallel Old 收集器結(jié)合進(jìn)行垃圾收集的示意圖,在新生代,當(dāng)用戶線程都執(zhí)行到安全點(diǎn)時,所有線程暫停執(zhí)行,ParNew 收集器以多線程,采用復(fù)制算法進(jìn)行垃圾收集工作,收集完之后,用戶線程繼續(xù)開始執(zhí)行;在老年代,當(dāng)用戶線程都執(zhí)行到安全點(diǎn)時,所有線程暫停執(zhí)行,Parallel Old 收集器以多線程,采用標(biāo)記整理算法進(jìn)行垃圾收集工作。
老年代垃圾收集器
1. Serial Old 收集器
Serial Old 是 Serial收集器的老年代版本,它同樣是一個單線程收集器,使用標(biāo)記-整理算法,這個收集器的主要意義也是供客戶端模式下HotSpot虛擬機(jī)使用。如果在服務(wù)端一種是與Parallel Scavenge收集器搭配使用,另外一種是作為CMS 收集器發(fā)生失敗時的后備預(yù)案。
Serial收集器與Serial Old收集器的運(yùn)行示意圖:
適用場景: Client模式;單核服務(wù)器;與Parallel Scavenge收集器搭配;作為CMS收集器的后備方案,在并發(fā)收集發(fā)生Concurrent Mode Failure時使用
2. Parallel Old收集器
Parallel Old 是 Parallel Scavenge收集器的老年代版本,支持多線程并發(fā)收集,基于標(biāo)記-整理算法實現(xiàn),可以充分利用多核CPU的計算能力,慮Parallel Scavenge/Parallel Old收集器運(yùn)行示意圖:
2. CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器。CMS收集器是基于標(biāo)記-清楚算法實現(xiàn)的,這個收集器的運(yùn)作過程比前面的幾個收集器更復(fù)雜一點(diǎn),整個過程分為四個步驟:
1) 初始標(biāo)記(CMS initial mark): 只是標(biāo)記 GC Roots能夠直接關(guān)聯(lián)到的對象,速度很快
2) 并發(fā)標(biāo)記(CMS concurrent mark): 從GC Roots 的直接關(guān)聯(lián)對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不需要停頓用戶線程,可以和垃圾收集線程一起并發(fā)運(yùn)行
3) 重新標(biāo)記(CMS remark): 修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)作導(dǎo)致標(biāo)記產(chǎn)生對象的標(biāo)記記錄,這個階段的停頓時間會比初始標(biāo)記階段稍長一些
4) 并發(fā)清除(CMS concurrent sweep): 清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對象,由于不需要移動存活對象,這個階段也是可以與用戶線程同時并發(fā)的。
其中 初始標(biāo)記、重新標(biāo)記這兩個步驟仍然需要 “Stop The World” 暫停所有用戶線程,由于在整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清理階段中,垃圾收集器線程都可以與用戶線程一起工作,總體來說,CMS收集器的內(nèi)存回收過程是和用戶線程一起并發(fā)執(zhí)行的,如下圖所示:
優(yōu)點(diǎn):
CMS收集器是一款優(yōu)秀的收集器,它主要體現(xiàn)為:并發(fā)收集、低停頓。
缺點(diǎn):
CMS收集器對處理器資源非常敏感,在并發(fā)階段,雖然不會導(dǎo)致用戶線程停頓,但也會因為占用一部分線程導(dǎo)致應(yīng)用程序變慢,降級總的吞吐量。CMS默認(rèn)啟動回收線程數(shù)是(處理器核心數(shù)量+3)/4,也就是說如果處理器核心數(shù)大于等于四個,并發(fā)回收時垃圾收集線程只占用不超過25%的處理器運(yùn)算資源,處理器資源會隨著CPU數(shù)量的增加而下降,但是當(dāng)CPU數(shù)量不足四個的時候,CMS對用戶程序的影響就可能變的很大。CMS收集器無法處理 “浮動垃圾” ,有可能出現(xiàn) “Concurrent Mode Failure” 失敗進(jìn)而導(dǎo)致另一次完全“Stop The World” 的Full GC 的產(chǎn)生,在CMS的并發(fā)標(biāo)記和并發(fā)清理階段,用戶線程是還在繼續(xù)進(jìn)行的,程序在運(yùn)行自然就還會伴隨有新的垃圾對象不斷產(chǎn)生,但這一部分垃圾對象是出現(xiàn)在標(biāo)記過程結(jié)束以后,CMS無法在當(dāng)次收集中處理掉它們,只好留待下一次垃圾收集時再清理掉。這一部分的垃圾就稱為“浮動垃圾” 因為CMS是一款基于 “標(biāo)記-清除”算法實現(xiàn)的收集器,因此收集結(jié)束時會有大量的空間碎片產(chǎn)生,空間碎片過多的時,將對給大對象帶來很大的麻煩,有可能不得不提前進(jìn)行Full GC的操作,不過通過參數(shù):-XX:+UseCMS-CompactAtFullCollection進(jìn)行優(yōu)化
新生代和老年代垃圾收集器
G1收集器
Garbage First (簡稱G1)收集器是垃圾收集器技術(shù)發(fā)展歷史上的里程碑式的成果,它開創(chuàng)了收集器面向局部收集的設(shè)計思路和基于Region的內(nèi)存布局形式。
G1收集器是一款面向服務(wù)器端應(yīng)用的垃圾收集器,在JDK9發(fā)布的時候成為服務(wù)端模式下的默認(rèn)垃圾收集器,而CMS則淪為不被推薦使用的收集器
特點(diǎn):
在G1收集器出現(xiàn)之前所有的其他收集器,目標(biāo)范圍要么是新生代要么是老年代,要么就是Java堆,但是G1做了全面性,它可以面向堆內(nèi)存任何部分來組成回收集進(jìn)行回收,衡量標(biāo)準(zhǔn)不再是它屬于哪個分代,而是那塊內(nèi)存中存放的垃圾數(shù)量最多,回收收益最大,這就是G1收集器的Mixed GC模式。而G1開創(chuàng)的基于Region的堆內(nèi)存布局是它能夠?qū)崿F(xiàn)這個目標(biāo)的關(guān)鍵。
雖然G1仍然保留了新生代和老年代的概念,但新生代和老年代不再是固定的,他們都是一系列區(qū)域的動態(tài)集合,G1可以建立可預(yù)測的停頓時間模型,是因為它將Region作為單次回收最小單元
G1不再堅持固定大小以及固定數(shù)量的分代區(qū)域劃分,而是把連續(xù)的Java堆劃分為多個大小相等的獨(dú)立區(qū)域,每一個Region都可以根據(jù)需要,扮演新生代的Eden空間、Survivor空間或者老年代空間,收集器能夠?qū)Π缪莶煌慕巧腞egion采用不同的策略去處理。
Region中海油一類特殊的Humongous區(qū)域,專門用來存儲大對象。G1認(rèn)為只要大小超過一個Region容量一半的對象即可判定為大對象。
G1收集器的運(yùn)行過程:
初始標(biāo)記(Initial Marking): 標(biāo)記GC Roots 能直接關(guān)聯(lián)到的對象,并且修改TAMS指針的值,讓下一階段用戶線程并發(fā)運(yùn)行時,能正確在可用的Region中分配新對象,需要耗時較短的停頓線程,但是是借用Minor GC的時候同步完成的,所以在這個階段實際沒有額外的停頓
并發(fā)標(biāo)記(Concurrent Marking): 從GC Roots 開始對堆中對象進(jìn)行可達(dá)性分析,遞歸掃描整個堆里面的對象圖,找出要回收的對象,這個階段耗時較長,但可以和用戶程序并發(fā)執(zhí)行。
最終標(biāo)記(Final Marking): 對用戶線程做另一個短暫的暫停,用戶處理并發(fā)階段結(jié)束后仍遺留下來的最后那少量的SATB記錄
篩選回收(Live Data Counting and Evacuation): 負(fù)責(zé)更新Region的統(tǒng)計數(shù)據(jù),對各個Region的回收價值和成本進(jìn)行排序,根據(jù)用戶鎖期望的停頓時間來制定回收計劃,可以只有選擇任意多個Region構(gòu)成回收集,然后把決定回收的那一部分Region的存活對象賦值到空的Region中,再清理整個Region的全部空間。
總結(jié)
小勇你懂了嗎?小勇小勇,你別睡著了啊,我還沒講完呢!小勇醒醒啊!!!
小勇迷迷糊糊的說:怎么了,下班了嗎?。。。。,下班啥,我講的GC你聽懂了嗎?
小勇:聽懂了,我明天就去面試,你講的太棒了!
敷衍,算了我已經(jīng)把東西都放在筆記里面了,你要是感興趣就可以來看看。
本文轉(zhuǎn)載自微信公眾號「牧小農(nóng)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系牧小農(nóng)公眾號。