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

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

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

1、引用計數算法
所謂引用計數算法就是,給一個對象定義一個引用計數器,每當該對象被引用一次引用計數器就加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.ui

2、可達性分析算法this

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

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

GC Root的選擇依據是:code

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

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

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

下面看一個栗子🌰

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;

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

相關文章
相關標籤/搜索