GC是什么?為什么要GC?JVM 垃圾回收算法有哪些?
圖片
1 Java垃圾回收機(jī)制(GC是什么?為什么要GC)
為了讓程序員更專注于代碼的實(shí)現(xiàn),而不用過多的考慮內(nèi)存釋放的問題,所以,在Java語(yǔ)言中,有了自動(dòng)的垃圾回收機(jī)制,也就是我們熟悉的GC(Garbage Collection)。
有了垃圾回收機(jī)制后,程序員只需要關(guān)心內(nèi)存的申請(qǐng)即可,內(nèi)存的釋放由系統(tǒng)自動(dòng)識(shí)別完成。
在進(jìn)行垃圾回收時(shí),不同的對(duì)象引用類型,GC會(huì)采用不同的回收時(shí)機(jī)
換句話說(shuō),自動(dòng)的垃圾回收的算法就會(huì)變得非常重要了,如果因?yàn)樗惴ǖ牟缓侠恚瑢?dǎo)致內(nèi)存資源一直沒有釋放,同樣也可能會(huì)導(dǎo)致內(nèi)存溢出的。
當(dāng)然,除了Java語(yǔ)言,C#、Python等語(yǔ)言也都有自動(dòng)的垃圾回收機(jī)制。
2 對(duì)象什么時(shí)候可以被垃圾器回收
圖片
簡(jiǎn)單一句就是:如果一個(gè)或多個(gè)對(duì)象沒有任何的引用指向它了,那么這個(gè)對(duì)象現(xiàn)在就是垃圾,如果定位了垃圾,則有可能會(huì)被垃圾回收器回收。
如果要定位什么是垃圾,有兩種方式來(lái)確定,第一個(gè)是引用計(jì)數(shù)法,第二個(gè)是可達(dá)性分析算法
2.1 引用計(jì)數(shù)法
一個(gè)對(duì)象被引用了一次,在當(dāng)前的對(duì)象頭上遞增一次引用次數(shù),如果這個(gè)對(duì)象的引用次數(shù)為0,代表這個(gè)對(duì)象可回收
String demo = new String("123");
圖片
String demo = null;
圖片
當(dāng)對(duì)象間出現(xiàn)了循環(huán)引用的話,則引用計(jì)數(shù)法就會(huì)失效
圖片
先執(zhí)行右側(cè)代碼的前4行代碼
圖片
目前上方的引用關(guān)系和計(jì)數(shù)都是沒問題的,但是,如果代碼繼續(xù)往下執(zhí)行,如下圖
圖片
雖然a和b都為null,但是由于a和b存在循環(huán)引用,這樣a和b永遠(yuǎn)都不會(huì)被回收。
優(yōu)點(diǎn):
- 實(shí)時(shí)性較高,無(wú)需等到內(nèi)存不夠的時(shí)候,才開始回收,運(yùn)行時(shí)根據(jù)對(duì)象的計(jì)數(shù)器是否為0,就可以直接回收。
- 在垃圾回收過程中,應(yīng)用無(wú)需掛起。如果申請(qǐng)內(nèi)存時(shí),內(nèi)存不足,則立刻報(bào)OOM錯(cuò)誤。
- 區(qū)域性,更新對(duì)象的計(jì)數(shù)器時(shí),只是影響到該對(duì)象,不會(huì)掃描全部對(duì)象。
缺點(diǎn):
- 每次對(duì)象被引用時(shí),都需要去更新計(jì)數(shù)器,有一點(diǎn)時(shí)間開銷。
- 浪費(fèi)CPU資源,即使內(nèi)存夠用,仍然在運(yùn)行時(shí)進(jìn)行計(jì)數(shù)器的統(tǒng)計(jì)。
- 無(wú)法解決循環(huán)引用問題,會(huì)引發(fā)內(nèi)存泄露。(最大的缺點(diǎn))
2.2 可達(dá)性分析算法
現(xiàn)在的虛擬機(jī)采用的都是通過可達(dá)性分析算法來(lái)確定哪些內(nèi)容是垃圾。
會(huì)存在一個(gè)根節(jié)點(diǎn)【GC Roots】,引出它下面指向的下一個(gè)節(jié)點(diǎn),再以下一個(gè)節(jié)點(diǎn)節(jié)點(diǎn)開始找出它下面的節(jié)點(diǎn),依次往下類推。直到所有的節(jié)點(diǎn)全部遍歷完畢。
根對(duì)象是那些肯定不能當(dāng)做垃圾回收的對(duì)象,就可以當(dāng)做根對(duì)象
局部變量,靜態(tài)方法,靜態(tài)變量,類信息
核心是:判斷某對(duì)象是否與根對(duì)象有直接或間接的引用,如果沒有被引用,則可以當(dāng)做垃圾回收
圖片
X,Y這兩個(gè)節(jié)點(diǎn)是可回收的,但是并不會(huì)馬上的被回收??! 對(duì)象中存在一個(gè)方法【finalize】。當(dāng)對(duì)象被標(biāo)記為可回收后,當(dāng)發(fā)生GC時(shí),首先會(huì)判斷這個(gè)對(duì)象是否執(zhí)行了finalize方法,如果這個(gè)方法還沒有被執(zhí)行的話,那么就會(huì)先來(lái)執(zhí)行這個(gè)方法,接著在這個(gè)方法執(zhí)行中,可以設(shè)置當(dāng)前這個(gè)對(duì)象與GC ROOTS產(chǎn)生關(guān)聯(lián),那么這個(gè)方法執(zhí)行完成之后,GC會(huì)再次判斷對(duì)象是否可達(dá),如果仍然不可達(dá),則會(huì)進(jìn)行回收,如果可達(dá)了,則不會(huì)進(jìn)行回收。
finalize方法對(duì)于每一個(gè)對(duì)象來(lái)說(shuō),只會(huì)執(zhí)行一次。如果第一次執(zhí)行這個(gè)方法的時(shí)候,設(shè)置了當(dāng)前對(duì)象與RC ROOTS關(guān)聯(lián),那么這一次不會(huì)進(jìn)行回收。 那么等到這個(gè)對(duì)象第二次被標(biāo)記為可回收時(shí),那么該對(duì)象的finalize方法就不會(huì)再次執(zhí)行了。
GC ROOTS:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
/**
* demo是棧幀中的本地變量,當(dāng) demo = null 時(shí),由于此時(shí) demo 充當(dāng)了 GC Root 的作用,demo與原來(lái)指向的實(shí)例 new Demo() 斷開了連接,對(duì)象被回收。
*/
public class Demo {
public static void main(String[] args) {
Demo demo = new Demo();
demo = null;
}
}
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
/**
* 當(dāng)棧幀中的本地變量 b = null 時(shí),由于 b 原來(lái)指向的對(duì)象與 GC Root (變量 b) 斷開了連接,所以 b 原來(lái)指向的對(duì)象會(huì)被回收,而由于我們給 a 賦值了變量的引用,a在此時(shí)是類靜態(tài)屬性引用,充當(dāng)了 GC Root 的作用,它指向的對(duì)象依然存活!
*/
public class Demo {
public static Demo a;
public static void main(String[] args) {
Demo b = new Demo();
b.a = new Demo();
b = null;
}
}
- 方法區(qū)中常量引用的對(duì)象
/**
* 常量 a 指向的對(duì)象并不會(huì)因?yàn)?demo 指向的對(duì)象被回收而回收
*/
public class Demo {
public static final Demo a = new Demo();
public static void main(String[] args) {
Demo demo = new Demo();
demo = null;
}
}
- 本地方法棧中 JNI(即一般說(shuō)的 Native 方法)引用的對(duì)象
3 JVM 垃圾回收算法有哪些?
3.1 標(biāo)記清除算法
標(biāo)記清除算法:是將垃圾回收分為2個(gè)階段,分別是標(biāo)記和清除。
1.根據(jù)可達(dá)性分析算法得出的垃圾進(jìn)行標(biāo)記
2.對(duì)這些標(biāo)記為可回收的內(nèi)容進(jìn)行垃圾回收
圖片
可以看到,標(biāo)記清除算法解決了引用計(jì)數(shù)算法中的循環(huán)引用的問題,沒有從root節(jié)點(diǎn)引用的對(duì)象都會(huì)被回收。
同樣,標(biāo)記清除算法也是有缺點(diǎn)的:
- 效率較低,標(biāo)記和清除兩個(gè)動(dòng)作都需要遍歷所有的對(duì)象,并且在GC時(shí),需要停止應(yīng)用程序,對(duì)于交互性要求比較高的應(yīng)用而言這個(gè)體驗(yàn)是非常差的。
- (重要)通過標(biāo)記清除算法清理出來(lái)的內(nèi)存,碎片化較為嚴(yán)重,因?yàn)楸换厥盏膶?duì)象可能存在于內(nèi)存的各個(gè)角落,所以清理出來(lái)的內(nèi)存是不連貫的。
3.2 復(fù)制算法
如果您覺得本文不錯(cuò),歡迎關(guān)注,點(diǎn)贊,收藏支持,您的關(guān)注是我堅(jiān)持的動(dòng)力!
復(fù)制算法的核心,將原有的內(nèi)存空間一分為二,每次只用其中的一塊,在垃圾回收時(shí),將正在使用的對(duì)象復(fù)制到另一個(gè)內(nèi)存空間中,然后將該內(nèi)存空間清空,交換兩個(gè)內(nèi)存的角色,完成垃圾的回收。
如果內(nèi)存中的垃圾對(duì)象較多,需要復(fù)制的對(duì)象就較少,這種情況下適合使用該方式并且效率比較高,反之,則不適合。
1)將內(nèi)存區(qū)域分成兩部分,每次操作其中一個(gè)。
2)當(dāng)進(jìn)行垃圾回收時(shí),將正在使用的內(nèi)存區(qū)域中的存活對(duì)象移動(dòng)到未使用的內(nèi)存區(qū)域。當(dāng)移動(dòng)完對(duì)這部分內(nèi)存區(qū)域一次性清除。
3)周而復(fù)始。
優(yōu)點(diǎn):
- 在垃圾對(duì)象多的情況下,效率較高
- 清理后,內(nèi)存無(wú)碎片
缺點(diǎn):
- 分配的2塊內(nèi)存空間,在同一個(gè)時(shí)刻,只能使用一半,內(nèi)存使用率較低
3.3 標(biāo)記整理算法
標(biāo)記壓縮算法:是在標(biāo)記清除算法的基礎(chǔ)之上,做了優(yōu)化改進(jìn)的算法。和標(biāo)記清除算法一樣,也是從根節(jié)點(diǎn)開始,對(duì)對(duì)象的引用進(jìn)行標(biāo)記,在清理階段,并不是簡(jiǎn)單的直接清理可回收對(duì)象,而是將存活對(duì)象都向內(nèi)存另一端移動(dòng),然后清理邊界以外的垃圾,從而解決了碎片化的問題。
圖片
1)標(biāo)記垃圾。
2)需要清除向右邊走,不需要清除的向左邊走。
3)清除邊界以外的垃圾。
優(yōu)缺點(diǎn)同標(biāo)記清除算法,解決了標(biāo)記清除算法的碎片化的問題,同時(shí),標(biāo)記壓縮算法多了一步,對(duì)象移動(dòng)內(nèi)存位置的步驟,其效率也有有一定的影響。
與復(fù)制算法對(duì)比:復(fù)制算法標(biāo)記完就復(fù)制,但標(biāo)記整理算法得等把所有存活對(duì)象都標(biāo)記完畢,再進(jìn)行整理
4 分代收集算法
4.1 概述
在java8時(shí),堆被分為了兩份:新生代和老年代【1:2】,在java7時(shí),還存在一個(gè)永久代。
對(duì)于新生代,內(nèi)部又被分為了三個(gè)區(qū)域。Eden區(qū),S0區(qū),S1區(qū)【8:1:1】
當(dāng)對(duì)新生代產(chǎn)生GC:MinorGC【young GC】
當(dāng)對(duì)老年代代產(chǎn)生GC:Major GC
當(dāng)對(duì)新生代和老年代產(chǎn)生FullGC: 新生代 + 老年代完整垃圾回收,暫停時(shí)間長(zhǎng),應(yīng)盡力避免
4.2工作機(jī)制
- 新創(chuàng)建的對(duì)象,都會(huì)先分配到eden區(qū)
- 當(dāng)伊甸園內(nèi)存不足,標(biāo)記伊甸園與 from(現(xiàn)階段沒有)的存活對(duì)象
- 將存活對(duì)象采用復(fù)制算法復(fù)制到 to 中,復(fù)制完畢后,伊甸園和 from 內(nèi)存都得到釋放
- 經(jīng)過一段時(shí)間后伊甸園的內(nèi)存又出現(xiàn)不足,標(biāo)記eden區(qū)域to區(qū)存活的對(duì)象,將存活的對(duì)象復(fù)制到from區(qū)
- 當(dāng)幸存區(qū)對(duì)象熬過幾次回收(最多15次),晉升到老年代(幸存區(qū)內(nèi)存不足或大對(duì)象會(huì)導(dǎo)致提前晉升)
MinorGC、 Mixed GC 、 FullGC的區(qū)別是什么
圖片
- MinorGC【young GC】發(fā)生在新生代的垃圾回收,暫停時(shí)間短(STW)
- Mixed GC 新生代 + 老年代部分區(qū)域的垃圾回收,G1 收集器特有
- FullGC: 新生代 + 老年代完整垃圾回收,暫停時(shí)間長(zhǎng)(STW),應(yīng)盡力避免?
名詞解釋:
STW(Stop-The-World):暫停所有應(yīng)用程序線程,等待垃圾回收的完成
5 說(shuō)一下 JVM 有哪些垃圾回收器?
在jvm中,實(shí)現(xiàn)了多種垃圾收集器,包括:
- 串行垃圾收集器
- 并行垃圾收集器
- CMS(并發(fā))垃圾收集器
- G1垃圾收集器
5.1 串行垃圾收集器
Serial和Serial Old串行垃圾收集器,是指使用單線程進(jìn)行垃圾回收,堆內(nèi)存較小,適合個(gè)人電腦
- Serial 作用于新生代,采用復(fù)制算法
- Serial Old 作用于老年代,采用標(biāo)記-整理算法
垃圾回收時(shí),只有一個(gè)線程在工作,并且java應(yīng)用中的所有線程都要暫停(STW),等待垃圾回收的完成。
圖片
5.2 并行垃圾收集器
Parallel New和Parallel Old是一個(gè)并行垃圾回收器,JDK8默認(rèn)使用此垃圾回收器
- Parallel New作用于新生代,采用復(fù)制算法
- Parallel Old作用于老年代,采用標(biāo)記-整理算法
垃圾回收時(shí),多個(gè)線程在工作,并且java應(yīng)用中的所有線程都要暫停(STW),等待垃圾回收的完成。
圖片
5.3 CMS(并發(fā))垃圾收集器
CMS全稱 Concurrent Mark Sweep,是一款并發(fā)的、使用標(biāo)記-清除算法的垃圾回收器,該回收器是針對(duì)老年代垃圾回收的,是一款以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,停頓時(shí)間短,用戶體驗(yàn)就好。其最大特點(diǎn)是在進(jìn)行垃圾回收時(shí),應(yīng)用仍然能正常運(yùn)行。
圖片
6 詳細(xì)聊一下G1垃圾回收器
6.1 概述
- 應(yīng)用于新生代和老年代,在JDK9之后默認(rèn)使用****G1
- 劃分成多個(gè)區(qū)域,每個(gè)區(qū)域都可以充當(dāng) eden,survivor,old, humongous,其中 humongous 專為大對(duì)象準(zhǔn)備
- 采用復(fù)制算法
- 響應(yīng)時(shí)間與吞吐量兼顧
- 分成三個(gè)階段:新生代回收、并發(fā)標(biāo)記、混合收集
- 如果并發(fā)失?。椿厥账俣融s不上創(chuàng)建新對(duì)象速度),會(huì)觸發(fā) Full GC
圖片
image-20230506154323950
6.2 Young Collection(年輕代垃圾回收)
- 初始時(shí),所有區(qū)域都處于空閑狀態(tài)
圖片
- 創(chuàng)建了一些對(duì)象,挑出一些空閑區(qū)域作為伊甸園區(qū)存儲(chǔ)這些對(duì)象
圖片
- 當(dāng)伊甸園需要垃圾回收時(shí),挑出一個(gè)空閑區(qū)域作為幸存區(qū),用復(fù)制算法復(fù)制存活對(duì)象,需要暫停用戶線程
圖片
圖片
- 隨著時(shí)間流逝,伊甸園的內(nèi)存又有不足
- 將伊甸園以及之前幸存區(qū)中的存活對(duì)象,采用復(fù)制算法,復(fù)制到新的幸存區(qū),其中較老對(duì)象晉升至老年代
圖片
圖片
圖片
6.3 Young Collection + Concurrent Mark (年輕代垃圾回收+并發(fā)標(biāo)記)
當(dāng)老年代占用內(nèi)存超過閾值(默認(rèn)是45%)后,觸發(fā)并發(fā)標(biāo)記,這時(shí)無(wú)需暫停用戶線程
圖片
- 并發(fā)標(biāo)記之后,會(huì)有重新標(biāo)記階段解決漏標(biāo)問題,此時(shí)需要暫停用戶線程。
- 這些都完成后就知道了老年代有哪些存活對(duì)象,隨后進(jìn)入混合收集階段。此時(shí)不會(huì)對(duì)所有老年代區(qū)域進(jìn)行回收,而是根據(jù)暫停時(shí)間目標(biāo)優(yōu)先回收價(jià)值高(存活對(duì)象少)的區(qū)域(這也是 Gabage First 名稱的由來(lái))。
圖片
6.4 Mixed Collection (混合垃圾回收)
復(fù)制完成,內(nèi)存得到釋放。進(jìn)入下一輪的新生代回收、并發(fā)標(biāo)記、混合收集
圖片
其中H叫做巨型對(duì)象,如果對(duì)象非常大,會(huì)開辟一塊連續(xù)的空間存儲(chǔ)巨型對(duì)象
圖片
7. JVM 垃圾回收面試
面試官:簡(jiǎn)述Java垃圾回收機(jī)制?(GC是什么?為什么要GC)
候選人:
為了讓程序員更專注于代碼的實(shí)現(xiàn),而不用過多的考慮內(nèi)存釋放的問題,所以,在Java語(yǔ)言中,有了自動(dòng)的垃圾回收機(jī)制,也就是我們熟悉的GC(Garbage Collection)。
有了垃圾回收機(jī)制后,程序員只需要關(guān)心內(nèi)存的申請(qǐng)即可,內(nèi)存的釋放由系統(tǒng)自動(dòng)識(shí)別完成。
在進(jìn)行垃圾回收時(shí),不同的對(duì)象引用類型,GC會(huì)采用不同的回收時(shí)機(jī)
面試官:對(duì)象什么時(shí)候可以被垃圾器回收
候選人:
如果一個(gè)或多個(gè)對(duì)象沒有任何的引用指向它了,那么這個(gè)對(duì)象現(xiàn)在就是垃圾,如果定位了垃圾,則有可能會(huì)被垃圾回收器回收。
如果要定位什么是垃圾,有兩種方式來(lái)確定,第一個(gè)是引用計(jì)數(shù)法,第二個(gè)是可達(dá)性分析算法
通常都使用可達(dá)性分析算法來(lái)確定是不是垃圾
面試官: JVM 垃圾回收算法有哪些?
候選人:
我記得一共有四種,分別是標(biāo)記清除算法、復(fù)制算法、標(biāo)記整理算法、分代回收
面試官: 你能詳細(xì)聊一下分代回收嗎?
候選人:
關(guān)于分代回收是這樣的
在java8時(shí),堆被分為了兩份:新生代和老年代,它們默認(rèn)空間占用比例是1:2
對(duì)于新生代,內(nèi)部又被分為了三個(gè)區(qū)域。Eden區(qū),S0區(qū),S1區(qū)默認(rèn)空間占用比例是8:1:1
具體的工作機(jī)制是有些情況:
1)當(dāng)創(chuàng)建一個(gè)對(duì)象的時(shí)候,那么這個(gè)對(duì)象會(huì)被分配在新生代的Eden區(qū)。當(dāng)Eden區(qū)要滿了時(shí)候,觸發(fā)YoungGC。
2)當(dāng)進(jìn)行YoungGC后,此時(shí)在Eden區(qū)存活的對(duì)象被移動(dòng)到S0區(qū),并且當(dāng)前對(duì)象的年齡會(huì)加1,清空Eden區(qū)。
3)當(dāng)再一次觸發(fā)YoungGC的時(shí)候,會(huì)把Eden區(qū)中存活下來(lái)的對(duì)象和S0中的對(duì)象,移動(dòng)到S1區(qū)中,這些對(duì)象的年齡會(huì)加1,清空Eden區(qū)和S0區(qū)。
4)當(dāng)再一次觸發(fā)YoungGC的時(shí)候,會(huì)把Eden區(qū)中存活下來(lái)的對(duì)象和S1中的對(duì)象,移動(dòng)到S0區(qū)中,這些對(duì)象的年齡會(huì)加1,清空Eden區(qū)和S1區(qū)。
5)對(duì)象的年齡達(dá)到了某一個(gè)限定的值(默認(rèn)15歲 ),那么這個(gè)對(duì)象就會(huì)進(jìn)入到老年代中。
當(dāng)然也有特殊情況,如果進(jìn)入Eden區(qū)的是一個(gè)大對(duì)象,在觸發(fā)YoungGC的時(shí)候,會(huì)直接存放到老年代
當(dāng)老年代滿了之后,觸發(fā)FullGC。FullGC同時(shí)回收新生代和老年代,當(dāng)前只會(huì)存在一個(gè)FullGC的線程進(jìn)行執(zhí)行,其他的線程全部會(huì)被掛起。 我們?cè)诔绦蛑幸M量避免FullGC的出現(xiàn)。
面試官:講一下新生代、老年代、永久代的區(qū)別?
候選人:
新生代主要用來(lái)存放新生的對(duì)象。
老年代主要存放應(yīng)用中生命周期長(zhǎng)的內(nèi)存對(duì)象。
永久代指的是永久保存區(qū)域。主要存放Class和Meta(元數(shù)據(jù))的信息。在Java8中,永久代已經(jīng)被移除,取而代之的是一個(gè)稱之為“元數(shù)據(jù)區(qū)”(元空間)的區(qū)域。元空間和永久代類似,不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存的限制。
面試官:說(shuō)一下 JVM 有哪些垃圾回收器?
候選人:
在jvm中,實(shí)現(xiàn)了多種垃圾收集器,包括:串行垃圾收集器、并行垃圾收集器(JDK8默認(rèn))、CMS(并發(fā))垃圾收集器、G1垃圾收集器(JDK9默認(rèn))
面試官:Minor GC、Major GC、Full GC是什么
候選人:
嗯,其實(shí)它們指的是不同代之間的垃圾回收
Minor GC 發(fā)生在新生代的垃圾回收,暫停時(shí)間短
Major GC 老年代區(qū)域的垃圾回收,老年代空間不足時(shí),會(huì)先嘗試觸發(fā)Minor GC。Minor GC之后空間還不足,則會(huì)觸發(fā)Major GC,Major GC速度比較慢,暫停時(shí)間長(zhǎng)
Full GC 新生代 + 老年代完整垃圾回收,暫停時(shí)間長(zhǎng),應(yīng)盡力避免