一、概述
說到垃圾回收,我們必須要知道什么是垃圾?為什么要回收?
- 什么是垃圾:垃圾是在程序運(yùn)行中沒有任何指針指向的對象,這個對象就是需要被回收的垃圾。
- 為什么要回收:在JVM中如果不及時對垃圾進(jìn)行回收,那么這些垃圾所占的內(nèi)存空間會一直保留到程序結(jié)束,這部分內(nèi)存空間無法被其他對象使用,當(dāng)內(nèi)存占滿的時候就會導(dǎo)致內(nèi)存溢出。
在Java中垃圾回收分為兩個步驟:找到垃圾和回收垃圾。
在找到垃圾的過程中,稱為標(biāo)記階段,其中有兩種算法:引用計數(shù)法,可達(dá)性分析法。
在回收垃圾的過程中,稱為清除階段,對應(yīng)的算法有:標(biāo)記-清除算法,復(fù)制算法,標(biāo)記-整理算法,分代收集算法。
二、引用計數(shù)法
- 原理
對每個對象保存一個整型的引用計數(shù)器屬性,每當(dāng)有一個地方引用它時,計數(shù)器+1,當(dāng)引用失效時,計數(shù)器-1,當(dāng)計數(shù)器為0時,說明這個對象不再使用,此時被判定為垃圾。
- 優(yōu)點(diǎn):實(shí)現(xiàn)簡單,判斷效率高。
- 缺點(diǎn):無法處理循環(huán)引用的情況,這是一個致命的缺點(diǎn),多以Java的垃圾回收器沒有使用這種算法。
循環(huán)引用:
舉一個很簡單的例子:鏈表,鏈表中有一個Next屬性,Next引用的時下一個對象,
A->B->C->D->E->A這種結(jié)構(gòu)。此時將A置空,那么BCDE的引用計數(shù)器還為1,這時就會導(dǎo)致BCDE永遠(yuǎn)無法回收。
三、可達(dá)性分析算法
- 原理
以根對象集合(GC Roots)為起始點(diǎn),按照從上到下的方式搜索被根對象集合所鏈接的目標(biāo)對象是否可達(dá)。
搜索的過程中的路徑稱為引用鏈。
如果對象沒有與任何引用鏈相連,則是不可達(dá)的,就意味著這個對象已經(jīng)死亡。

可作為GC Roots的有:
- 虛擬機(jī)棧中引用的對象。
- 本地方法棧內(nèi)JNI引用的對象。
- 方法去區(qū)靜態(tài)屬性引用的對象。
- 方法區(qū)中常量引用的對象。
在經(jīng)過可達(dá)性分析之后,僅僅進(jìn)行了第一次標(biāo)記,接下來還有第二次標(biāo)記。
- 如果對象沒有重寫finalize()方法,則虛擬機(jī)視為沒有必要執(zhí)行,對象被判定不可達(dá)。
- 如果對象重寫了finalize()方法,且還未執(zhí)行過,則對象會被插入到F-Queue隊列中,由虛擬機(jī)中一個低優(yōu)先級的Finalizer線程觸發(fā)finalize()方法執(zhí)行。
- 在finalize()執(zhí)行的過程中,GC會對F-Queue中的對象進(jìn)行第二次標(biāo)記,如果此對象與引用鏈上的任何一個對象建立了聯(lián)系,那么該對象就會被移出"即將回收"集合,此對象將不會被回收,如果再次出現(xiàn)沒有引用鏈的情況下,finalize()方法不會再調(diào)用,對象直接變成不可達(dá),就是說finalize()方法只會執(zhí)行一次。
四、標(biāo)記-清除算法
- 原理
當(dāng)堆中的有效內(nèi)存空間被耗盡時,就會停止整個程序(Stop The World),然后進(jìn)行兩項(xiàng)工作,標(biāo)記和清除。
標(biāo)記:GC從引用根節(jié)點(diǎn)開始遍歷,標(biāo)記所有被引用的對象。
清除:遍歷完成后,如果有對象沒有被標(biāo)記為可達(dá)對象,則將其清除。
這里的清除并不是真正的清除,而是把需要清除的對象地址保存在空閑列表,當(dāng)有新的對象需要插入時,判斷垃圾的位置空間是否足夠,如果夠,則插入。就相當(dāng)于覆蓋。

- 優(yōu)點(diǎn):實(shí)現(xiàn)簡單。
- 缺點(diǎn):效率不高,清理之后的內(nèi)存空間不連續(xù),產(chǎn)生內(nèi)存碎片。需要維護(hù)一個空閑列表。
五、復(fù)制算法
- 原理
將內(nèi)存空間分為兩塊,每次只使用其中一塊,在垃圾回收時將正在使用中的存活對象復(fù)制到未使用的內(nèi)存塊中,然后清除正在使用的內(nèi)存塊的所有對象。最終完成垃圾回收。

- 優(yōu)點(diǎn):沒有標(biāo)記和清除過程,實(shí)現(xiàn)簡單,運(yùn)行高效。復(fù)制后保證了空間連續(xù)性,不會出現(xiàn)碎片問題。
- 缺點(diǎn):需要兩倍的空間或者說犧牲一倍的內(nèi)存空間。還要維護(hù)對象的引用關(guān)系,不管是內(nèi)存還是時間開銷都不小。
如果系統(tǒng)中存活對象非常多,復(fù)制算法則需要復(fù)制更多的對象,反而優(yōu)點(diǎn)變成缺點(diǎn),所以復(fù)制算法可以用在數(shù)據(jù)比較小的內(nèi)存中,比如堆中的新生代。
六、標(biāo)記-整理算法
- 原理
標(biāo)記整理算法分為三個步驟,標(biāo)記,整理,刪除,它比標(biāo)記-刪除算法多了一步:整理。
標(biāo)記:從根節(jié)點(diǎn)開始標(biāo)記所有被引用的對象。
整理:將所有存活的對象都向一段移動。
刪除:清除邊界外所有的空間。

- 優(yōu)點(diǎn):
- 解決了標(biāo)記-清除算法的空間不連續(xù)的缺點(diǎn)。
- 解決了復(fù)制算法的內(nèi)存減半的代價。
- 缺點(diǎn):
- 效率低于復(fù)制算法
- 移動對象的同時,還需要調(diào)整對象的引用。
七、三種清除算法對比
| 標(biāo)記-清除 | 標(biāo)記-整理 | 復(fù)制 | |
|---|---|---|---|
| 速度 | 中等 | 最慢 | 最快 |
| 空間開銷 | 少(會堆積碎片) | 少(不堆積碎片) | 對象的2被大小(不堆積碎片) |
| 移動對象 | 否 | 是 | 是 |
八、分代收集算法
上面總結(jié)了三種回收算法的優(yōu)缺點(diǎn),每種算法都有利有弊,比如復(fù)制算法,雖然它是最快的,但是它會浪費(fèi)一半的空間,所以根據(jù)這些優(yōu)缺點(diǎn),衍生出了分代收集算法。
在JVM堆中,分為新生代和老年代,所以分代收集算法就是針對他們的特點(diǎn)使用最適合的回收算法。
新生代:區(qū)域相對較小,對象生命周期短,存活率低,回收頻繁。自帶Eden和兩個Survivor區(qū),適用復(fù)制算法。
老年代:區(qū)域較大,對象生命周期長,存活率高,回收頻率低。因此適用標(biāo)記-清除或標(biāo)記-整理算法。