原文地址:https://xeblog.cn/articles/23
確定可回收對象
引用計數(shù)法
給對象中添加一個引用計數(shù)器,每當(dāng)有一個地方引用這個對象的時候,計數(shù)器的值就加1;當(dāng)對象的引用失效時,計數(shù)器的值就減1;任何時刻計數(shù)器為0的對象就被判定為可回收的對象。
存在兩個對象之間互相循環(huán)引用的問題
Object obj1 = obj2
Object obj2 = obj1
對象 obj1 引用了對象 obj2 ,對象 obj2 又引用著對象 obj1,兩個對象互相引用,使得計數(shù)器都不能為0,這種情況如果使用 引用計數(shù)法 就無法判斷對象是否是可回收的。
可達性分析算法
通過一系列的稱為 GC Roots 的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為 引用鏈 ,當(dāng)一個對象到 GC Roots 沒有任何 引用鏈 相連時,則證明此對象是可回收的。
如圖所示,對象 Object 5、Object 6、Object 7 雖然互相有關(guān)聯(lián),但是它們到 GC Roots 是不可達的,所以它們將會被暫定為是可回收的對象。但此時判定并沒有完全結(jié)束,因為判定一個對象是否是可回收是需要經(jīng)過兩次標(biāo)記過程的,上述是第一次的標(biāo)記過程,第二次標(biāo)記則是將第一次標(biāo)記的對象進行一次篩選:通過判斷該對象是否需要執(zhí)行 finalize() 方法。如果該對象覆蓋了 finalize() 方法,并且系統(tǒng)沒有運行過該方法(finalize() 方法全局只運行一次),則表示該對象需要執(zhí)行finalize() 方法,那么這個對象將會被放入到一個叫 F-Queue 的隊列中,等待虛擬機中的一個低優(yōu)先級的Finalizer 線程去執(zhí)行它。在執(zhí)行 finalize() 方法的過程中,只要該對象沒有和 GC Roots 引用鏈上的任何一個對象有關(guān)聯(lián),這個對象才能被真正的定為是可回收的對象。

可以作為 GC Roots 的對象:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象;
- 方法區(qū)中類靜態(tài)屬性引用的對象;
- 方法區(qū)中常量引用的對象;
- 本地方法棧中JNI(Native方法)引用的對象。
JVM就是采用 可達性分析算法 來判斷對象是否是可回收的。
回收方法區(qū)
方法區(qū)處于JVM的永久代區(qū)域,且永久代的垃圾回收效率遠低于堆中(尤其是在新生代中)的回收效率,對于這塊區(qū)域主要是回收兩部分內(nèi)容:廢棄常量和無用類。
常量回收
回收常量與回收J(rèn)ava堆中的對象非常類似。如果當(dāng)前系統(tǒng)中沒有任何一個變量與常量A相等,常量A也沒有在任何地方被引用,則常量A就是可回收的。
類回收
判斷一個類是否可回收是非??量痰模枰瑫r滿足3個條件:
- 該類的所有實例都已經(jīng)被回收,Java堆中不存在該類的任何實例;
- 加載該類的
ClassLoader已經(jīng)被回收; - 該類對應(yīng)的
java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
垃圾回收算法
標(biāo)記-清除算法
首先通過 可達性分析算法 標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。
缺點
- 標(biāo)記和清除的效率都不高。
- 標(biāo)記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致程序在運行過程中需要分配較大對象時,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。
復(fù)制算法
將可用內(nèi)存按容量分為大小相等的兩塊,每次只使用其中的一塊,當(dāng)這一塊內(nèi)存用完時,就將還存活的對象復(fù)制到另外一塊上,然后把已使用過的內(nèi)存空間一次性清理掉。這樣使得每次都是對整個半?yún)^(qū)進行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動堆頂指針,按順序分配內(nèi)存即可,簡單高效。
缺點
- 將可用內(nèi)存縮小為原來的一半了。
優(yōu)點
- 解決了內(nèi)存碎片的問題。
- 新生代中采用這種算法效率較高。
標(biāo)記-整理算法
復(fù)制收集算法在對象存活率較高時就要進行較多的復(fù)制操作,效率將會變低,老年代一般不能直接選用這種算法。
根據(jù)老年代的特點,提出了一種“標(biāo)記-整理”算法,標(biāo)記過程與“標(biāo)記-清除”算法一樣,不同的是這種算法不直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。
缺點
- 標(biāo)記和整理的效率都不高。
優(yōu)點
- 解決了內(nèi)存碎片的問題。
分代回收算法
根據(jù)對象存活周期的不同,將 Java堆內(nèi)存 劃分為 新生代 和 老年代,這樣可以根據(jù)各個年代的特點采用最適當(dāng)?shù)幕厥账惴ā?/p>
新生代: 每次垃圾回收時都發(fā)現(xiàn)有大批對象死去,只有少量對象存活,可采用 復(fù)制算法,只需要付出少量存活對象的復(fù)制成本就可以完成回收。
老年代: 對象存活率高,沒有額外空間對它進行分配擔(dān)保,必須使用 標(biāo)記-清除 或 標(biāo)記-整理 算法進行回收。
參考
- 《深入理解Java虛擬機:JVM高級特性與最佳實踐 第二版》