1 如何判斷對象爲“垃圾”
在垃圾回收之前,首先要判斷哪些對象爲“垃圾”,也就是判斷哪些對象已經死去了,這裏面的死去的概念就是這個對象不可能再被任何途徑使用。
1.1 引用計數器法
一個很簡單、效率也很高的方法。給對象添加一個引用計數器,每當有一個地方引用它時,計數器+1;當引用失效時,計數器-1;任何時刻計算器值爲0的對象即爲不再被使用的對象。但是Java虛擬機沒有使用這種方式,因爲它無法解決循環引用的問題。事實上,當程序中存在循環引用的時候,JVM依然有很好的收集效果。所以JVM並沒有採用這種方法
1.2 可達性分析法
目前Java、C#均採用的主流分析法。基本思想就是通過一系列的稱爲”GC ROOTs”(set集合)的對象作爲起始點,從這些節點開始向下搜索,搜索過的路徑稱爲引用鏈。當一個對象沒有任何引用鏈相連的時候,則證明對象是不可用的。
圖中, Object5、Object6、Object7是GCROOT的不可達對象 他們可能會被回收。
在Java中,可作爲GCROOT的對象包括以下幾種:
- 虛擬機棧中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區中常量引用的對象
-
本地方法棧中引用的對象
2 引用的概念
在Java1.2之前,對於引用的定義很簡單:如果reference類型的數據中保存的是另外一個對象的地址,則這塊內存代表一個引用。這種方式不能很好的說明對象的重要性,有時我們希望在系統資源不足的時候,回收一些對象,而當系統資源充足的時候,不對這些對象進行管理。於是在1.2之後,出現了關於引用的四個概念
- 強引用:代碼中普遍存在的 如“Objectobject=new Object()“這種形式,這類稱爲強引用。只要強引用還在,垃圾收集器永遠不會進行收集
- 軟引用:用來描述一些還有用的非必需的對象。對於這一類對象,只有在系統發生內存溢出前引發的回收,纔會把這些類對象納入到回收範圍之內。如果這些回收還沒有足夠的內存,則會發生內存溢出。
- 弱引用:描述非必須對象,強度比軟引用還要弱一點。這一些對象只能生存到下一次回收前,只要垃圾收集器工作,他們就會被回收
- 虛引用:一個對象的虛引用幾乎沒有意義,也不能通過虛引用去獲得一個對象。爲對象設置虛引用的唯一目的就是在這個對象被回收的時候收到一個系統通知。
3 對象的死亡判定
3.1 兩次標記判定死亡
對象經過可達性分析以後,還不能立即判定爲死亡對象,要宣告一個對象死亡,至少需要經過兩次標記過程。
(1) 如果對象經過可達性分析,發現它已經不與任何引用鏈相連接,那麼它會被第一次標記並且進行篩選,篩選的條件是對這個對象是否有必要執行finalize方法。如果對象沒有覆蓋finalize方法或者已經被虛擬機執行過finalize方法,就稱爲沒有必要。
(2) 如果有必要執行,對象就會被放進一個F-QUEUE的隊列之中,並在稍後由虛擬機創建一個優先級較低的線程去執行,也就是調用finalize方法。但是不會等待它結束,因爲如果finalize方法執行緩慢或者發生死循環,將會把這個執行線程掛起,這將會導致虛擬機的回收系統異常。
3.2 對象的自救復活
從上面可以知道,對象如果在finalize方法中把自己與某個引用鏈關聯起來,就可以進行“自救”,即把自己移除出“即將回收”的集合中。如果這個對象還沒有逃脫,那麼它將被回收了。
public class StillAlive
{
static StillAlive hook=null;
@Override
protected void finalize() throws Throwable
{
super.finalize();
System.out.println("finalize invoked" );
//自救
hook=this;
}
public void isAlive()
{
System.out.println("i am still alive");
}
public static void main(String[] args) throws Throwable
{
hook=new StillAlive();
hook=null;
//第一次收集 自救成功 finalize 方法調用
System.gc();
Thread.sleep(500);
if(hook!=null)
{
hook.isAlive();
}
else {
System.out.println("i am dead");
}
hook=null;
//第二次收集 自救失敗 finalize沒有調用
System.gc();
Thread.sleep(500);
if(hook!=null)
{
hook.isAlive();
}
else {
System.out.println("i am dead");
}
}
}
從上面代碼可以看出,對象的finalize方法確實被GC收集器觸發過,而且僅僅觸發了一次。並且在這一次觸發的時候,成功逃脫了。然後第二次卻失敗了,這是因爲系統僅僅會調用finalize方法一次,如果下一次面臨回收的時候,是不會重複執行finalize方法的。