原文地址:https://xeblog.cn/articles/23
確定可回收對象
引用計(jì)數(shù)法
給對象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用這個(gè)對象的時(shí)候,計(jì)數(shù)器的值就加1肮塞;當(dāng)對象的引用失效時(shí),計(jì)數(shù)器的值就減1;任何時(shí)刻計(jì)數(shù)器為0的對象就被判定為可回收的對象千康。
存在兩個(gè)對象之間互相循環(huán)引用的問題
Object obj1 = obj2
Object obj2 = obj1
對象 obj1
引用了對象 obj2
,對象 obj2
又引用著對象 obj1
铲掐,兩個(gè)對象互相引用拾弃,使得計(jì)數(shù)器都不能為0,這種情況如果使用 引用計(jì)數(shù)法
就無法判斷對象是否是可回收的摆霉。
可達(dá)性分析算法
通過一系列的稱為 GC Roots
的對象作為起始點(diǎn)豪椿,從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為 引用鏈
携栋,當(dāng)一個(gè)對象到 GC Roots
沒有任何 引用鏈
相連時(shí)搭盾,則證明此對象是可回收的。
如圖所示婉支,對象 Object 5鸯隅、Object 6、Object 7
雖然互相有關(guān)聯(lián)向挖,但是它們到 GC Roots
是不可達(dá)的蝌以,所以它們將會被暫定為是可回收的對象。但此時(shí)判定并沒有完全結(jié)束何之,因?yàn)榕卸ㄒ粋€(gè)對象是否是可回收是需要經(jīng)過兩次標(biāo)記過程的跟畅,上述是第一次的標(biāo)記過程,第二次標(biāo)記則是將第一次標(biāo)記的對象進(jìn)行一次篩選:通過判斷該對象是否需要執(zhí)行 finalize()
方法溶推。如果該對象覆蓋了 finalize()
方法徊件,并且系統(tǒng)沒有運(yùn)行過該方法(finalize()
方法全局只運(yùn)行一次),則表示該對象需要執(zhí)行finalize()
方法悼潭,那么這個(gè)對象將會被放入到一個(gè)叫 F-Queue
的隊(duì)列中庇忌,等待虛擬機(jī)中的一個(gè)低優(yōu)先級的Finalizer
線程去執(zhí)行它。在執(zhí)行 finalize()
方法的過程中舰褪,只要該對象沒有和 GC Roots
引用鏈上的任何一個(gè)對象有關(guān)聯(lián)皆疹,這個(gè)對象才能被真正的定為是可回收的對象。
可以作為 GC Roots
的對象:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象占拍;
- 方法區(qū)中類靜態(tài)屬性引用的對象略就;
- 方法區(qū)中常量引用的對象捎迫;
- 本地方法棧中JNI(Native方法)引用的對象。
JVM就是采用 可達(dá)性分析算法
來判斷對象是否是可回收的表牢。
回收方法區(qū)
方法區(qū)處于JVM的永久代區(qū)域窄绒,且永久代的垃圾回收效率遠(yuǎn)低于堆中(尤其是在新生代中)的回收效率,對于這塊區(qū)域主要是回收兩部分內(nèi)容:廢棄常量和無用類崔兴。
常量回收
回收常量與回收J(rèn)ava堆中的對象非常類似彰导。如果當(dāng)前系統(tǒng)中沒有任何一個(gè)變量與常量A相等,常量A也沒有在任何地方被引用敲茄,則常量A就是可回收的位谋。
類回收
判斷一個(gè)類是否可回收是非常苛刻的堰燎,需要同時(shí)滿足3個(gè)條件:
- 該類的所有實(shí)例都已經(jīng)被回收掏父,Java堆中不存在該類的任何實(shí)例;
- 加載該類的
ClassLoader
已經(jīng)被回收秆剪; - 該類對應(yīng)的
java.lang.Class
對象沒有在任何地方被引用赊淑,無法在任何地方通過反射訪問該類的方法。
垃圾回收算法
標(biāo)記-清除算法
首先通過 可達(dá)性分析算法
標(biāo)記出所有需要回收的對象仅讽,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象陶缺。
缺點(diǎn)
- 標(biāo)記和清除的效率都不高。
- 標(biāo)記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片洁灵,空間碎片太多可能會導(dǎo)致程序在運(yùn)行過程中需要分配較大對象時(shí)组哩,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。
復(fù)制算法
將可用內(nèi)存按容量分為大小相等的兩塊处渣,每次只使用其中的一塊伶贰,當(dāng)這一塊內(nèi)存用完時(shí),就將還存活的對象復(fù)制到另外一塊上罐栈,然后把已使用過的內(nèi)存空間一次性清理掉黍衙。這樣使得每次都是對整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況荠诬,只要移動堆頂指針琅翻,按順序分配內(nèi)存即可,簡單高效柑贞。
缺點(diǎn)
- 將可用內(nèi)存縮小為原來的一半了方椎。
優(yōu)點(diǎn)
- 解決了內(nèi)存碎片的問題。
- 新生代中采用這種算法效率較高钧嘶。
標(biāo)記-整理算法
復(fù)制收集算法在對象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作棠众,效率將會變低,老年代一般不能直接選用這種算法。
根據(jù)老年代的特點(diǎn)闸拿,提出了一種“標(biāo)記-整理”算法空盼,標(biāo)記過程與“標(biāo)記-清除”算法一樣,不同的是這種算法不直接對可回收對象進(jìn)行清理新荤,而是讓所有存活的對象都向一端移動揽趾,然后直接清理掉端邊界以外的內(nèi)存。
缺點(diǎn)
- 標(biāo)記和整理的效率都不高苛骨。
優(yōu)點(diǎn)
- 解決了內(nèi)存碎片的問題篱瞎。
分代回收算法
根據(jù)對象存活周期的不同,將 Java堆內(nèi)存
劃分為 新生代
和 老年代
痒芝,這樣可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)幕厥账惴ā?/p>
新生代: 每次垃圾回收時(shí)都發(fā)現(xiàn)有大批對象死去奔缠,只有少量對象存活,可采用 復(fù)制算法
吼野,只需要付出少量存活對象的復(fù)制成本就可以完成回收。
老年代: 對象存活率高两波,沒有額外空間對它進(jìn)行分配擔(dān)保瞳步,必須使用 標(biāo)記-清除
或 標(biāo)記-整理
算法進(jìn)行回收。
參考
- 《深入理解Java虛擬機(jī):JVM高級特性與最佳實(shí)踐 第二版》