JVM——垃圾回收機(jī)制

哪些內(nèi)存需要回收

由于程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧的生命周期都跟隨線程的生命周期,當(dāng)線程銷毀了,內(nèi)存也就回收了,所以這幾個(gè)區(qū)域不用過(guò)多地考慮內(nèi)存回收。由于堆和方法區(qū)的內(nèi)存都是動(dòng)態(tài)分配的,而且是線程共享的,所以內(nèi)存回收主要關(guān)注這部分區(qū)域。

如何判斷對(duì)象是否存活

引用計(jì)數(shù)法

給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1,如果引用失效,計(jì)數(shù)器值減1,所以當(dāng)該計(jì)數(shù)器的值為0時(shí),就表示該對(duì)象可以被回收了。但是存在兩個(gè)對(duì)象之間相互循環(huán)引用的問(wèn)題。

可達(dá)性分析算法

通過(guò)一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到“GC Roots”沒(méi)有任何引用鏈相連的話,也就是GC Roots到這個(gè)對(duì)象不可達(dá)時(shí),證明此對(duì)象已經(jīng)不可用,可以被回收了。

二次標(biāo)記

在可達(dá)性分析算法中被判斷是對(duì)象不可達(dá)時(shí)不一定會(huì)被垃圾回收機(jī)制回收,因?yàn)橐嬲嬉粋€(gè)對(duì)象的死亡,必須經(jīng)歷兩次標(biāo)記的過(guò)程。如果發(fā)現(xiàn)對(duì)象不可達(dá)時(shí),將會(huì)進(jìn)行第一次標(biāo)記,此時(shí)如果該對(duì)象調(diào)用了finalize()方法,那么這個(gè)對(duì)象會(huì)被放置在一個(gè)叫F-Queue的隊(duì)列之中,如果在此隊(duì)列中該對(duì)象沒(méi)有成功拯救自己(拯救自己的方法是該對(duì)象有沒(méi)有被重新引用),那么GC就會(huì)對(duì)F-Queue隊(duì)列中的對(duì)象進(jìn)行小規(guī)模的第二次標(biāo)記,一旦被第二次標(biāo)記的對(duì)象,將會(huì)被移除隊(duì)列并等待被GC回收,所以finalize()方法是對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì)。

垃圾回收算法

標(biāo)記—清除算法

首先標(biāo)記出需要回收的對(duì)象,在標(biāo)記完成后進(jìn)行統(tǒng)一的回收(標(biāo)記即二次標(biāo)記的過(guò)程)。此算法有兩個(gè)不足:一是效率問(wèn)題,標(biāo)記和清除兩個(gè)過(guò)程效率都不高;二是空間問(wèn)題,標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,內(nèi)存空間碎片太多的話會(huì)導(dǎo)致以后程序在運(yùn)行中想要分配較大對(duì)象的時(shí)候,無(wú)法找到一塊連續(xù)的內(nèi)存空間而導(dǎo)致不得不進(jìn)行又一次的GC回收(后續(xù)的垃圾回收算法都是基于此算法進(jìn)行改進(jìn)的)。

標(biāo)記—清除算法

復(fù)制算法

把內(nèi)存按容量劃分為大小相等的兩塊區(qū)域,每次只使用其中的一塊,當(dāng)這一塊的內(nèi)存空間用完了,就把還存活的對(duì)象復(fù)制到另一塊內(nèi)存中去,然后把已經(jīng)使用的過(guò)的內(nèi)存空間一次性清理掉。這樣每次都是對(duì)半個(gè)內(nèi)存區(qū)域進(jìn)行GC回收,并不會(huì)產(chǎn)生內(nèi)存碎片,但是代價(jià)是把內(nèi)存縮小了一半,效率比較低。

復(fù)制算法

標(biāo)記—整理算法

標(biāo)記算法一樣,區(qū)別是清除的時(shí)候會(huì)把所有存活的對(duì)象向一端移動(dòng)(向上和向左),然后清除掉端邊界以外的內(nèi)存。

標(biāo)記—整理算法

分代收集算法

根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊(新生代或老生代),然后根據(jù)每個(gè)年代的特點(diǎn)采用最合適的收集算法。比如在新生代中,每次都有大量對(duì)象死去,就選擇復(fù)制算法;而在老生代中對(duì)象的生存率高,沒(méi)有額外的空間為它進(jìn)行分配擔(dān)保,所以采用標(biāo)記—清除算法或者標(biāo)記—整理算法來(lái)進(jìn)行回收。

垃圾回收器

CMS收集器

CMS收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的垃圾收集器,是基于“標(biāo)記——清除”算法實(shí)現(xiàn)的,其回收過(guò)程主要分為四個(gè)步驟:

(1)初始標(biāo)記:標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快;
(2)并發(fā)標(biāo)記:進(jìn)行GC Roots Tracing的過(guò)程,也就是標(biāo)記不可達(dá)的對(duì)象,相對(duì)耗時(shí);
(3)重新標(biāo)記:修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作導(dǎo)致的標(biāo)記變動(dòng),速度比較快;
(4)并發(fā)清除:對(duì)標(biāo)記的對(duì)象進(jìn)行統(tǒng)一回收處理,比較耗時(shí);

由于初始標(biāo)記和重新標(biāo)記速度比較快,其它工作線程停頓的時(shí)間幾乎可以忽略不計(jì),所以CMS的內(nèi)存回收過(guò)程是與用戶線程一起并發(fā)執(zhí)行的。
初始標(biāo)記和重新標(biāo)記兩個(gè)步驟需要Stop the world;并發(fā)標(biāo)記和并發(fā)清除兩個(gè)步驟可與用戶線程并發(fā)執(zhí)行。??
“Stop the world”意思是垃圾收集器在進(jìn)行垃圾回收時(shí),會(huì)暫停其它所有工作線程,直到垃圾收集結(jié)束為止。

CMS的缺點(diǎn)

(1)對(duì)CPU資源非常敏感;也就是說(shuō)當(dāng)CMS開啟垃圾收集線程進(jìn)行垃圾回收時(shí),會(huì)占用部分用戶線程,如果在CPU資源緊張的情況下,會(huì)導(dǎo)致用戶程序的工作效率下降。

(2)無(wú)法處理浮動(dòng)垃圾導(dǎo)致又一次FULL GC的產(chǎn)生;由于CMS并發(fā)回收垃圾時(shí)用戶線程同時(shí)也在運(yùn)行,伴隨用戶線程的運(yùn)行自然會(huì)有新的垃圾產(chǎn)生,這部分垃圾出現(xiàn)在標(biāo)記過(guò)程之后,CMS無(wú)法在當(dāng)次收集過(guò)程中進(jìn)行回收,只能在下一次GC時(shí)在進(jìn)行清除。所以在CMS運(yùn)行期間要確保內(nèi)存中有足夠的預(yù)留空間用來(lái)存放用戶線程的產(chǎn)生的浮動(dòng)垃圾,不允許像其它收集器一樣等到老年代區(qū)完全填滿了之后再進(jìn)行收集;那么當(dāng)內(nèi)存預(yù)留的空間不足時(shí)就會(huì)產(chǎn)生又一次的FULL GC來(lái)釋放內(nèi)存空間,由于是通過(guò)Serial Old收集器進(jìn)行老年代的垃圾收集,所以導(dǎo)致停頓的時(shí)間變長(zhǎng)了(系統(tǒng)有一個(gè)閾值來(lái)觸發(fā)CMS收集器的啟動(dòng),這個(gè)閾值不允許太高,太高反而導(dǎo)致性能降低)。

(3)標(biāo)記——清除算法會(huì)產(chǎn)生內(nèi)存碎片;如果產(chǎn)生過(guò)多的內(nèi)存碎片時(shí),當(dāng)系統(tǒng)虛擬機(jī)想要再分配大對(duì)象時(shí),會(huì)找不到一塊足夠大的連續(xù)內(nèi)存空間進(jìn)行存儲(chǔ),不得不又一次觸發(fā)FULL GC。

G1收集器

G1收集器是一款成熟的商用的垃圾收集器,是基于“標(biāo)記——整理”算法實(shí)現(xiàn)的,其回收過(guò)程主要分為四個(gè)步驟:

(1)初始標(biāo)記:標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快;
(2)并發(fā)標(biāo)記:進(jìn)行GC Roots Tracing的過(guò)程,也就是標(biāo)記不可達(dá)的對(duì)象,相對(duì)耗時(shí)?;
(3)最終標(biāo)記:修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作導(dǎo)致的標(biāo)記變動(dòng),速度比較快;
(4)篩選回收:首先對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時(shí)間來(lái)制定回收計(jì)劃;

G1收集器的特點(diǎn)

(1)并發(fā)與并行:機(jī)型垃圾收集時(shí)可以與用戶線程并發(fā)運(yùn)行;
(2)分代收集:能根據(jù)對(duì)象的存活時(shí)間采取不同的收集算法進(jìn)行垃圾回收;
(3)不會(huì)產(chǎn)生內(nèi)存碎片:基于標(biāo)記——整理算法和復(fù)制算法保證不會(huì)產(chǎn)生內(nèi)存空間碎片;
(4)可預(yù)測(cè)的停頓:G1除了追求低停頓時(shí)間外,還能建立可預(yù)測(cè)的停頓時(shí)間模型,便于用戶的實(shí)時(shí)監(jiān)控;

CMS收集器與G1收集器的區(qū)別

(1)CMS采用標(biāo)記——清除算法會(huì)產(chǎn)生空間碎片,G1采用標(biāo)記——整理算法不會(huì)產(chǎn)生空間碎片;
(2)G1可以建立可預(yù)測(cè)的停頓時(shí)間模型,而CMS則不能;

內(nèi)存分配策略

新生代:新生代包含一個(gè)Eden區(qū)和兩個(gè)Survivor區(qū)。大多數(shù)情況下,新創(chuàng)建的對(duì)象會(huì)在新生代Eden區(qū)中分配,當(dāng)Eden區(qū)沒(méi)有足夠的空間進(jìn)行分配時(shí),虛擬機(jī)會(huì)觸發(fā)一次Minor GC,如果Survivor區(qū)空間允許的話,該對(duì)象將被分配到Survivor。

老年代:大對(duì)象直接在老年代中分配,大對(duì)象指需要大量連續(xù)內(nèi)存空間的Java對(duì)象。

分代收集如何判定對(duì)象的年齡?

虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器,如果對(duì)象在Eden區(qū)出生并經(jīng)過(guò)第一次Minor GC后仍然存活,并且能被Survivor容納的話,將被移動(dòng)到Survivor空間,并且對(duì)象年齡設(shè)置為1,。對(duì)象在Survivor區(qū)中每熬過(guò)一次Minor GC,年齡就增加1歲,當(dāng)對(duì)象的年齡增加到一定程度(默認(rèn)為15歲),就會(huì)被晉升到老年代中。

為什么新生代中要有Survivor區(qū)?

防止頻繁觸發(fā)FULL GC。如果沒(méi)有Survivor,Eden區(qū)每進(jìn)行一次Minor GC,存活的對(duì)象就會(huì)被送到老年代,這樣會(huì)使老年代很快被填滿,導(dǎo)致老年代觸發(fā)FULL GC,由于老年代的內(nèi)存空間遠(yuǎn)大于新生代,所以進(jìn)行一次Full GC消耗的時(shí)間比Minor GC長(zhǎng)得多。

為什么要設(shè)置兩個(gè)Survivor區(qū)?

防止產(chǎn)生內(nèi)存空間碎片。如果只有Survivor1,那么每一次當(dāng)Eden區(qū)滿時(shí),觸發(fā)Minor GC并把對(duì)象移入Survivor1中,如此循環(huán)對(duì)導(dǎo)致Survivor1中產(chǎn)生大量的空間碎片;所以需要有Survivor2,當(dāng)Eden再一次滿時(shí),觸發(fā)Minor GC,虛擬機(jī)會(huì)把 Eden中和Survivor1中的存活對(duì)象通過(guò)復(fù)制算法移入Survivor2中,這樣Survivor2就不會(huì)產(chǎn)生內(nèi)存碎片,同時(shí)Eden和Survivor1會(huì)清理內(nèi)存,保證下一次Minor GC觸發(fā)時(shí)的操作。

Minor GC和Full GC的區(qū)別?

(1)新生代GC(Minor GC):指發(fā)生在新生代的垃圾收集動(dòng)作,因?yàn)镴ava對(duì)象大多數(shù)都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度比較快。
(2)老年代GC(Full GC):指發(fā)生在老年代的垃圾收集動(dòng)作,速度非常慢,所以要盡量減少Full GC的發(fā)生。

引用類型

強(qiáng)引用:指向使用new關(guān)鍵字創(chuàng)建的對(duì)象的引用都是強(qiáng)引用,只要該對(duì)象的強(qiáng)引用還在,該對(duì)象永遠(yuǎn)都不會(huì)被GC回收;

軟引用:當(dāng)內(nèi)存不足時(shí),就會(huì)被回收;

弱引用:只要發(fā)生GC,就會(huì)被回收;

虛引用:隨時(shí)都會(huì)被回收;


參考

《深入理解Java虛擬機(jī)》第2版 第3章 垃圾收集器與內(nèi)存分配策略

推薦閱讀

讓你徹底明白JAVA中堆與棧的區(qū)別

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容