JVM 中判斷物件是否 “存活” 的演算法 —— 可達性分析演算法
阿新 • • 發佈:2018-11-09
在堆中,幾乎存放著所有的物件例項,那麼回收這些物件例項時,我們需要判斷哪些物件是 “已死” 可以回收的,哪些物件是 “存活” 不需要回收的,下面就來介紹一下 JVM 中如何判斷上述問題的。
基本思路
通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連時,則證明此物件是不可用的。
哪些可以作為GC Roots
- 虛擬機器棧(棧幀中的本地變量表)中引用的物件。
- 方法區中類靜態屬性引用的物件。
- 方法區中常量引用的物件。
- 本地方法棧中JNI(即一般說的Native方法)引用的物件。
物件自我拯救
注意,當一個物件例項被標記為不可達物件時,並不是一定會被回收,只是將該物件新增到回收的列表中。
那當被新增到回收列表中時,如果將自身從該列表移除呢?也就是說將該物件重新變成有用的物件呢?
Java Object類提供了一個物件自我救贖的方法:
protected void finalize();//當垃圾回收器確定不存在對該物件的更多引用時,由物件的垃圾回收器呼叫此方法。
如果物件要在 finalize() 中成功拯救自己——只要重新與引用鏈上的任何一個物件建立關聯即可,譬如把自己(this關鍵字)賦值給某個類變數或者物件的成員變數。
但是注意,finalize()方法只會被呼叫一次!
我們來看一下下面的例子:
/**
* 此程式碼演示了兩點:
* 1.物件可以在被GC時自我拯救。
* 2.這種自救的機會只有一次,因為一個物件的finalize()方法最多隻會被系統自動呼叫一次
*/
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes,i am still alive :)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize mehtod executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable {
SAVE_HOOK = new FinalizeEscapeGC();
// 物件第一次成功拯救自己
SAVE_HOOK = null;
System.gc();
// 因為finalize方法優先順序很低,所以暫停0.5秒以等待它
Thread.sleep(500);
if (null != SAVE_HOOK) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no,i am dead :(");
}
// 下面這段程式碼與上面的完全相同,但是這次自救卻失敗了
SAVE_HOOK = null;
System.gc();
// 因為finalize方法優先順序很低,所以暫停0.5秒以等待它
Thread.sleep(500);
if (null != SAVE_HOOK) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no,i am dead :(");
}
}
}
執行結果如下:
finalize mehtod executed!
yes,i am still alive :)
no,i am dead :(
本文的內容、圖片和程式碼參考自:《深入理解Java虛擬機器:JVM高階特性與最佳實踐》