首頁  >  文章  >  Java  >  Java中回收物件的標記以及物件的二次標記過程詳解

Java中回收物件的標記以及物件的二次標記過程詳解

黄舟
黄舟原創
2017-10-11 10:12:431709瀏覽

這篇文章主要介紹了淺談Java回收對象的標記和對象的二次標記過程的相關內容,小編覺得還是挺不錯的,這裡給大家分享一下,需要的朋友可以參考。

一、物件的標記

1、什麼是標記?怎麼標記? 

        第一題相信大家都知道,標記就是對一些已死的對像打上記號,方便垃圾收集器的清理。 至於怎麼標記,一般有兩種方法:引用計數和可達性分析。

引用計數實現起來比較簡單,就是給物件添加一個引用計數器,每當有一個地方引用它時就加1,引用失效時就減1,當計數器為0的時候就標記為可回收。這種判斷效率很高,但是很多主流的虛擬機器並沒有採用這種方法,主要是因為它很難解決幾個物件之間循環引用的問題,雖然不怎麼用了,但還是值得我們學習!


public class Test {
private Object obj;
Public static void main(){
Test t1=new Test();
Test t2=new Test();
t1.obj=t2;
t2.obj=t1;
t1=null;
t2=null;
//如果对象在这行发生gc,那么t1和t2对象是否能被回收
System.gc();
}
}

 可達性分析的基本想法是:透過將一些稱為"GC Roots"的物件作為起始點,從這些節點開始搜索,搜索和該節點發生直接或間接引用關係的對象,將這些對像以鏈的形式組合起來,形成一張“關係網”,又叫做引用鏈。最後垃圾收集器就回收一些不在這張關係網上的物件。如圖:

連接GC Roots對象的object是確定還存活的對象,而右邊的die obj由於和GCROOTS沒有關係,所以會標記為可回收的對象。目前主流的商用虛擬機器用的都是類似的方法。那什麼對象才能當「GC Roots」呢?在java中,有四種物件可以作為「GC Roots」

        1:堆疊幀(第一章的名詞)中的引用對象。 (堆疊中的)

        2:靜態屬性所引用的物件。 (方法區中的)

        3:常數引用的物件。 (方法區中的)

        4:本地方法堆疊中JNI所引用的物件。 (本地方法堆疊中的)

二、物件的二次回收

說過物件的標記,但是不是被標記了就一定會被回收呢?不知道小夥伴們記不記得Object類別有一個finalize()方法,所有類別都繼承了Object類,因此也預設實作了這個方法。

finalize的工作原理應該是這樣的:一旦垃圾收集器準備好釋放物件所佔用的儲存空間,它首先呼叫finalize(),而且只有在下一次垃圾收集過程中,才會真正回收物件的記憶體.所以如果使用finalize(),就可以在垃圾收集期間進行一些重要的清除或清掃工作.
finalize()在什麼時候被呼叫?

有三種情況

1.所有物件被Garbage Collection時自動呼叫,例如執行System.gc()的時候.

2.程式退出時為每個物件呼叫一次finalize方法。

3.明確的呼叫finalize方法

這個方法的用途是:在該物件被回收之前,該物件的finalize()方法會被調用。 這裡的回收之前指的就是被標記之後,問題就出在這裡,有沒有一種情況就是原本一個物件開始不再上一章所講的「關係網」(引用鏈)中,但是當開發者重寫了finalize()後,並且將該物件重新加入到了「關係網」中,也就是說該物件對我們還有用,不應該被回收,但是已經被標記啦,怎麼辦呢?

        針對這個問題,虛擬機器的做法是進行兩次標記,也就是第一次標記不在「關係網」中的物件。第二次的話就要先判斷該物件有沒有實作finalize()方法了,如果沒有實作就直接判斷該物件可回收;如果實作了就會先放在一個佇列中,並由虛擬機建立的一個低優先順序的執行緒去執行它,隨後就會進行第二次的小規模標記,在這次被標記的物件就會真正的被回收了。

總結:簡單說,物件先進行第一次標記,在下一次GC之前會執行物件的finalize()方法。在執行finalize()方法的時候判斷物件是否實作了finalize()方法,沒有實作直接清除;實作了,將物件放在一個佇列中執行finalize方法,進行第二次標記
在java根搜尋演算法中判斷對象的可及性,對於不可達的對象,也不一定是必須清理。這時候有一個緩刑期,真正的判斷一個對象死亡,至少要經過倆次標記過程:如果對像在進行根搜索後發現沒有與GC roots相關聯的引用鏈,那他將會第一次標記並且進行一次篩選,篩選的條件是此物件是否有必要執行finalize()方法,當物件沒有覆寫finalize()方法,或finalize()方法已經被虛擬機器呼叫過,虛擬機器將這倆種情況都視為「沒有必要執行」。

即當一個物件重寫了finalize()方法的時候,這個物件被判定為有必要執行finalize()方法,那麼這個物件被放置在F-Queue隊列之中,並在稍後由一條由虛擬機器自動建立的、低優先順序的Finalizer執行緒去執行。這裡所謂的執行是指虛擬機會出發這個方法,但不承諾會等待它運行結束。這樣做的原因:如果一個物件在finalize()方法中執行緩慢,或者發生了死循環(極端的情況下),將可能會導致F-Queue隊列中的其他物件永久處於等待狀態,甚至導致整個內存回收系統崩潰。 finalize()方法是物件逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的物件進行第二次小規模的標記,如果物件要在finalize()中成功拯救自己----只要重新與引用鏈上的任何建立關聯即可,那麼在第二次標記時它將會被移出「即將回收」的集合;如果物件此時沒有逃脫,就會被回收。程式碼範例:參考《深入理解java虛擬機器》對應章節

總結

#

以上是Java中回收物件的標記以及物件的二次標記過程詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn