JAVA判斷一個對象生存還是死亡


JAVA中判斷一個對象是否死亡的算法有兩種:

  • 引用計數算法
  • 可達性分析算法

一、引用計數算法
所謂引用計數算法就是,給一個對象定義一個引用計數器,每當該對象被引用一次引用計數器就加1,如果一個對象的引用計數器為0,則說明這個對象已死。但是這種算法不是很嚴謹,因為當兩個對象互相引用的時候,如果我將它們設置為null,此時對象是可以被回收的,但是因為它的引用計數器不為0,證明它還沒死,沒死就不能被回收,如下面的栗子🌰

public class ReferenceCountAlgorithm {
    public Object instance = null;
    private static final int MB = 1024 * 1024;

    //  定義一個2MB的字節數組
    private byte[] arraySize = new byte[2 * MB];

    public static void main(String[] args) {
        // 創建兩個對象並互相引用
        ReferenceCountAlgorithm refA = new ReferenceCountAlgorithm();
        ReferenceCountAlgorithm refB = new ReferenceCountAlgorithm();
        refA.instance = refB;
        refB.instance = refA;

        refA = null;
        refB = null;
        // 此時兩個對象的引用數不為0,但是都為null,看能否被回收?
        System.gc();
    }

上面代碼中兩個對象互相引用,所以它們的引用計數器不為0,將兩個對象設置為null之后進行GC,查看GC日志:

Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for bsd-amd64 JRE (1.8.0_151-b12), built on Sep  5 2017 19:37:08 by "java_re" with gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Memory: 4k page, physical 8388608k(187584k free)

/proc/meminfo:

CommandLine flags: -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912 -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
0.097: [GC (System.gc())  7311K->392K(502784K), 0.0007996 secs]
0.098: [Full GC (System.gc())  392K->276K(502784K), 0.0034846 secs]

從上面紅色部分可以看出GC時將兩個對象回收了,所以並不能說明當一個對象的引用計數不為0的時候它就不會被GC.

二、可達性分析算法

所謂可達性算法,就是存活的對象會形成一棵以GC Root為根節點的樹,順着這棵樹往下遍歷,從根到葉子節點的路徑稱為引用鏈,如果一個對象存在這樣的引用鏈能夠通過它到達根節點或通過根節點找到它,說明這個對象可達,可達的對象不會被GC。畫個圖解釋一下:

上圖中紅色圈所代表的對象,它們雖然互相之間有關系,但是因為沒有到達GC Root的引用鏈,就能被GC掉;相反,綠色圈代表的對象因為有到達GC Root的引用鏈,說明它們是可達的,GC時這些對象就不會被回收;

GC Root的選擇依據是:

  • 虛擬機棧中引用的對象;
  • 方法區中的靜態屬性引用的對象;
  • 方法區中的常量引用的對象;
  • 本地方法棧中引用的對象;

 其實,在可達性算法中,一個不可達的對象也並非非死不可,因為它還有一個緩刑期,什么是緩刑期呢?就是延遲處死,當一個對象不可達的時候,並不會馬上被GC掉,而是會根據某些條件將它放到一個緩刑隊列中,標記它是要延遲處死的對象,那延遲處死到底是什么時候呢?其實一個對象會被標記兩次,第一次是看這個對象是否滿足加入緩刑隊列的條件,這個條件就是該對象是否覆蓋了Finalize方法及是否已經被JVM執行過這個方法,如果沒有就會被加到緩刑隊列中,這是第一次標記;第二次標記就是如果它沒有在Finalize方法中自救成功(可以和GC Root進行關聯),這時因為Finalize方法已經執行過了,所以會被GC。

所以,一個對象沒有引用鏈不代表它會馬上被GC,還要通過判斷是否覆蓋並執行過Finalize方法來決定是否有必要延遲處死,只有當它沒有執行過Finalize方法並且在Finalize方法中自救成功,才能保證不被GC,但是當第二次GC的時候因為執行過Finalize方法,所以已經失去了自救的機會,就會被GC,此時才能證明該對象真的死了!

下面看一個栗子🌰

public class SavedByFinalize {

    public static SavedByFinalize sbf = null;

    public void isAlive() {
        System.out.println("I am alive");
    }

    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Finalized method is execute");
        SavedByFinalize.sbf = this;// 自救
    }

    public static void main(String[] args) throws Throwable {
        sbf = new SavedByFinalize();

        /**
         * 對象第一次被GC
         */
        sbf = null;// 此時可以被回收
        System.gc();// 第一次進行垃圾回收,此時因為覆蓋了Finalized方法並進行自救,所以gc失敗
        Thread.sleep(1000);// 因為Finalized優先級低,所以需要等待它執行
        if (sbf != null) {
            sbf.isAlive();
        } else {
            System.out.println("I am dead");
        }

        /**
         * 對象第二次被GC
         */
        sbf = null;// 此時可以被回收
        System.gc();// 第一次進行垃圾回收,此時因為Finalized方法已經執行過了,所以gc成功
        Thread.sleep(1000);// 因為Finalized優先級低,所以需要等待它執行
        if (sbf != null) {
            sbf.isAlive();
        } else {
            System.out.println("I am dead");
        }
    }

}

上面的代碼中:

  • 當第一次進行GC時,雖然對象sbf為null,但是因為它還沒有執行過Finalize方法,所以會被加入緩刑隊列,因為在執行Finalize方法時自救成功,所以不會被GC;
  • 當第二次進行GC時,因為Finalize方法已經執行過,而且對象sbf為nul,所以它會直接被GC;

以上就是判斷一個對象是否真的已死的過程!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM