一,如何判斷對象是可回收的對象?
通過可達(dá)性分析(Reachability Analysis)來判定對象是否存活的.
這個算法的基本思路是通過一些列的稱為"GC Roots"的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個對象到GC Roots沒有任何引用鏈相連接時,則證明此對象是不可用的.
Java語言中,可作為GC Roots的對象主要包括下面幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中JNI(即一般說的Native方法)引用的對象.
二,如何判斷對象是死亡的?
即使在可達(dá)性分析算法中不可達(dá)的對象,也并非是“非死不可”的圆凰,這時候它們暫時處于“緩刑”階段忆家,要真正宣告一個對象死亡,至少要經(jīng)歷兩次標(biāo)記過程:如果對象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈卷哩,那它將會被第一次標(biāo)記并且進(jìn)行一次篩選昂灵,篩選的條件是此對象是否有必要執(zhí)行finalize()方法色鸳。當(dāng)對象沒有覆蓋finalize()方法琉历,或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”水醋。
如果這個對象被判定為有必要執(zhí)行finalize()方法旗笔,那么這個對象將會放置在一個叫做F-Queue的隊(duì)列之中,并在稍后由一個由虛擬機(jī)自動建立的拄踪、低優(yōu)先級的Finalizer線程去執(zhí)行它蝇恶。這里所謂的“執(zhí)行”是指虛擬機(jī)會觸發(fā)這個方法,但并不承諾會等待它運(yùn)行結(jié)束惶桐,這樣做的原因是撮弧,如果一個對象在finalize()方法中執(zhí)行緩慢,或者發(fā)生了死循環(huán)(更極端的情況)姚糊,將很可能會導(dǎo)致F-Queue隊(duì)列中其他對象永久處于等待贿衍,甚至導(dǎo)致整個內(nèi)存回收系統(tǒng)崩潰。finalize()方法是對象逃脫死亡命運(yùn)的最后一次機(jī)會救恨,稍后GC將對F-Queue中的對象進(jìn)行第二次小規(guī)模的標(biāo)記贸辈,如果對象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個對象建立關(guān)聯(lián)即可,譬如把自己(this關(guān)鍵字)賦值給某個類變量或者對象的成員變量肠槽,那在第二次標(biāo)記時它將被移除出“即將回收”的集合擎淤;如果對象這時候還沒有逃脫,那基本上它就真的被回收了秸仙。
示例代碼如下:
public class FinalizeEscapeGc{
public static FinalizeEscapeGc SAVE_HOOK = null;
public void isAlive(){
System.out.println("yes, i am still alive:");
}
protected void finalize() throws Throwable{
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGc.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable{
SAVE_HOOK = new FinalizeEscapeGc();
SAVE_HOOK = null;
System.gc();
Thread.sleep(5000);
//第一次逃脫
if(SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else{
System.out.println("no, i am dead:");
}
//第二次沒有逃脫
SAVE_HOOK = null;
System.gc();
if(SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else{
System.out.println("no i am dead-------------");
}
}
}
備注:finalize()介紹
- finalize()作為根類Object的受保護(hù)方法.
- 當(dāng)垃圾回收器確定不存在對該對象的更多引用時嘴拢,由對象的垃圾回收器調(diào)用此方法。子類重寫 finalize 方法寂纪,以配置系統(tǒng)資源或執(zhí)行其他清除.
- 對于任何給定對象席吴,Java 虛擬機(jī)最多只調(diào)用一次 finalize 方法
三,垃圾回收算法
1,標(biāo)記--清除算法(Mark-Sweep)
- A,算法思路
該回收算法分為兩個階段 標(biāo)記 和 清除
首先標(biāo)記處所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象.標(biāo)記--清除是最基礎(chǔ)的收集算法,后續(xù)的收集算法都是基于這種思路并對其不足進(jìn)行改進(jìn)而得到的
- B,算法的不足
-
效率問題
標(biāo)記和清除的效率都不高 -
空間問題
標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致以后在程序運(yùn)行過程中需要分配較大對象時,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作.
-
效率問題
2,復(fù)制算法
- A,算法思路
該算法是將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中一塊.當(dāng)這塊內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉.每次只對整個半?yún)^(qū)進(jìn)行內(nèi)存回收
-
B,算法優(yōu)缺點(diǎn)
- 內(nèi)存分配不必考慮內(nèi)存碎片問題
- 內(nèi)存分配簡單高效
- 但是將可使用的內(nèi)存減少到了原來的一半
-
C,算法的應(yīng)用
- 在商業(yè)虛擬機(jī)中,都采用這種收集算法來回收新生代.新生代的大部分內(nèi)存都是"朝生夕死"的,所以沒必要按照1:1的比例來劃分內(nèi)存空間,而是將內(nèi)存分為一塊交大 的Eden(伊甸園)空間和兩塊較小的Survivor(from survivor, to survivor),每次使用Eden和from Survivor空間. 當(dāng)回收時, 將Eden和from Survivor中還存活著的對象一次性地復(fù)制到另外to Survivor空間上,最后清理掉Eden和from Survivor空間.
- 但若出現(xiàn)Eden和from Survivor中存活的對象大于to Survivor怎么辦呢?這就需要依賴其他內(nèi)存進(jìn)行(例如老年代)進(jìn)行分配擔(dān)保(Handle Promotion). 當(dāng)to Survivor空間沒有足夠空間存放新生代收集下來的存活對象時,這些對象將直接通過分配擔(dān)保機(jī)制進(jìn)入老年代.
3,標(biāo)記---整理(Mark-Compact)
- A,算法思路
該算法的標(biāo)記過程仍然與"標(biāo)記---清除"算法一樣, 但后續(xù)步驟不是直接對可回收對象進(jìn)行清理,而是讓所有存活的對象都想一端移動,然后直接清理掉端邊界以外的內(nèi)存.
-B,算法應(yīng)用
主要應(yīng)用老年代垃圾回收
4,分代收集算法
當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用"分代收集"算法,主要根據(jù)對象存活周期的不同內(nèi)存劃分為幾塊.一般把堆分為新生代和老年代,根據(jù)不同年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴?
在新生代中,由于每次垃圾收集時都會有大批對象死去,只有少量存活,那就選用復(fù)制算法.
在老年代中因?yàn)閷ο蟠婊钚矢?沒有額外空間對它進(jìn)行分配擔(dān)保,就必須使用"標(biāo)記---清理"或者"標(biāo)記---整理"來進(jìn)行回收
參考<<深入理解Java虛擬機(jī) JVM高級特性與最佳實(shí)踐 第二版 周志明>>