如何判斷java對象已經死亡?
容易想到的就是引用計數算法,就說的是給對象添加一個引用計數器,每當有一個地方引用到他,就加1;引用失效就減1。但是這樣做是有問題的。
看下例子:
public class ReferenceCountingGC {
public Object instace = null;
// 一個200M的對象
private byte[] bigSize = new byte[200*1024*1024];
public static void main(String[] args) {
// 引用加1
ReferenceCountingGC a = new ReferenceCountingGC();
ReferenceCountingGC b = new ReferenceCountingGC();
// 引用再加1
a.instace=b;
b.instace=a;
// 如果是引用計數,那麼a,b的引用爲2
// 在這裏a,b減1
a = null;
b = null;
// 那麼在這裏ab引用都不是0,應該不可以回收
System.gc();
sleep(30);
}
public static void sleep(int second){
try {
Thread.sleep(second*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
debug,通過VisualVM發現,內存仍然被回收了。
,所以JVM不是通過引用計數的方式來確定是否回收對象的。因爲,引用計數從上面的例子看出來會有一個問題就是,雖然還有引用,但這兩個對象都是不可達對象(無法被訪問的)。
所以java的垃圾回收不是採用這種簡陋的引用計數來實現的。
可達性分析算法
java是通過可達性分析(Reachability Analysis)算法來判斷對象是否存活。
其基本思想是通過一系列“”GC Roots“”的對象爲起始點,從這些節點向下搜索,搜索所走過的路稱爲引用鏈,當一個對象沒有任何引用鏈相連時,則證明該對象是不可引用的。如圖(o1,o2,o3爲不可達對象):
GC Root對象
1.虛擬機棧中引用的對象。
2.方法區中類靜態屬性引用的對象。
3.方法區常量引用的對象。
4.本地方法棧中JNI引用的對象。
引用分類
強引用(StrongReference),軟引用(SoftReference),弱引用(WeakReference)以及PhantomReference(虛引用)
1.如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。
2.軟引用他的特點是當內存足夠時不會回收這種引用類型的對象,只有當內存不夠用時纔會回收。
3.弱引用的作用在於解決強引用所帶來的對象之間在存活時間上的耦合關係。弱引用最常見的用處是在集合類中,尤其在哈希表中。哈希表的接口允許使用任何Java對象作爲鍵來使用。當一個鍵值對被放入到哈希表中之後,哈希表對象本身就有了對這些鍵和值對象的引用。
4.虛引用的特點是隻要GC一運行就會把他給回收了,但會得到一個系統通知。
不可達對象就一定死亡嗎?
其實不可達對象也並非是‘’非死不可‘’,但是他們已經處於緩刑狀態。
要真的宣佈一個對象死亡要經過兩次標記過程:第一次標記並且篩選,篩選的條件是是否有必要執行了finalize方法。如果該類重寫了該方法,並且沒有執行過,那麼該方法將會被執行。如果該方法執行完成,對象又重新和GCRoot關聯上(把自己的引用給GCRoot),那麼他在下一次GC時就不會被回收。
具體來看一個例子。
public class FinalEscapeGC {
public static FinalEscapeGC obj = null;
public void isAlive(){
System.out.println("yes I'm still alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalEscapeGC.obj = this;
}
/**
* finalize method executed!
yes I'm still alive
no I'm dead!
*/
public static void main(String[] args) throws InterruptedException {
obj = new FinalEscapeGC();
// 第一次GC自救成功
obj = null;
System.gc();
// finalize方法優先級很低,因此要等他執行,不行還沒執行就被回收了
Thread.sleep(500);
if(obj == null)
System.out.println("no I'm dead!");
else
obj.isAlive();
// 第二次自救失敗
obj = null;
System.gc();
Thread.sleep(500);
if(obj == null)
System.out.println("no I'm dead!");
else
obj.isAlive();
}
}
這個例子說明兩點:
1,finalize方法是對象最後一個活下去的機會;
2,機會只能使用一次。
警告:finalize方法知道就行,千萬別用,可以使用finally代替
總結:
完整的從JVM垃圾回收的角度分析了對象是否死亡,對象是否該回收等知識點。要點:使用可達性分析算法發現對象是否可回收,GCRoot引用有哪些。引用的分級,finalize方法啥時候調用。