Java中的引用

我們知道Java中的引用類型有四種:強引用(strong reference)、軟引用(soft reference)、弱引用(weak reference)以及虛引用(phantom reference)。這四種引用類型使得Java中的對象有五種可達(Reachability)狀態(tài):強可達(strongly reachable)、軟可達(softly reachable)、弱可達(weak reachable)、虛可達(phantom reachable)以及不可達(unreachable)。對象的可達性將會影響到該對象是否會被垃圾回收器回收,以及何時被回收。

這些可達性到底代表什么意思?它和四種引用類型有什么關(guān)系?

GC Roots

想要了解對象的可達性,首先要知道一個概念:GC Roots。(主要參考了《深入理解Java虛擬機》一書的3.2.2-3.2.4章節(jié))

JVM在GC時要進行可達性分析,這個可達性就是相對于GC Roots的。GC Roots是JVM選定的一些對象,將這些對象作為起始點,搜索這些對象引用到的對象,接著再搜索新搜索到的對象所引用的對象...直到搜索不到任何新的對象。從GC Roots到某個可達對象的路徑稱為該對象的引用鏈(Reference Chain),對象的引用鏈并不唯一,一是因為一個對象可能會被多個GC Roots搜索到,二是同一個GC Root可能有不同的搜索路徑訪問到該對象。所有搜索到的對象,對于GC Roots來說都是可達的(可能是強可達,也可能是弱可達等等),所有未搜索到的但仍存活的對象都是不可達的,不可達的對象都是可回收的。但是并不是所有可達的對象就一定不會被回收,所有不可達的對象也不是一定會被回收(因為存在不可達對象重新變成可達對象的可能)。

Java中,可以作為GC Roots的對象包括以下幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象。
  • 方法區(qū)中類靜態(tài)屬性引用的變量。
  • 方法區(qū)中常量引用的對象。
  • 本地方法棧中引用的native對象。

可達性

前面說的以GC Roots為起始點做的可達性分析只將對象的可達性分成兩種:可達和不可達。對于可達對象還可以細分為四種可達狀態(tài),雖然都是可達對象,但是JVM對不同可達狀態(tài)下的對象是否進行回收以及何時進行回收是要區(qū)別對待的。

下面根據(jù)可達狀態(tài)從強到弱依次介紹它們:

  • 強可達對象:該對象存在這么一條引用鏈,這條引用鏈中的所有引用類型都是強引用,那么這個對象就是強可達的。什么樣的引用類型是強引用類型?Java中普通的對象引用類型就是強引用類型。軟引用、弱引用、虛引用類型需要通過java.lang.ref包下的SoftReference、WeakReference、PhantomReference類顯示構(gòu)造。所以一個對象的一條引用鏈中如果沒有使用任何Reference對象(強引用沒有對應(yīng)的引用類型類),那它就是強可達的。GC時,強可達的對象是一定不會被回收的。
  • 軟可達對象:對象不是強可達的,但是存在這么一條引用鏈,這個引用鏈中除了強引用類型外,只有軟引用類型,不存在弱引用或虛引用類型。GC時一般不會回收軟可達對象,只有在JVM將要發(fā)生OOM之前才會對軟可達對象進行回收。
  • 弱可達對象:對象既不是強可達的也不是軟可達的,但是對象的引用鏈中存在弱引用類型,當然也可以存在強引用和軟引用類型,但不存在虛引用類型。當對象處于弱可達狀態(tài)時,它會被標記為可回收,并在當前這一次GC時對它進行回收。
  • 虛可達對象:對象既不是強可達的,也不是軟可達的或弱可達的,對象的每一條引用鏈中都至少存在一個虛引用類型。當對象處于虛可達狀態(tài)時,說明它已經(jīng)被回收了。

引用類型

我們已經(jīng)知道除了強引用類型外,其它的引用類型都需要通過指定的java.lang.ref.Reference的子類來構(gòu)造:

public class ReferenceType {

    private static class Foo {

    }

    public static void main(String[] args) {
        // 強引用類型
        Foo foo = new Foo();

        // 軟引用類型
        SoftReference<Foo> fooSoftReference = new SoftReference<>(new Foo());
        SoftReference<Foo> fooSoftReference1 = new SoftReference<>(new Foo(), new ReferenceQueue<>());

        // 弱引用類型
        WeakReference<Foo> fooWeakReference = new WeakReference<>(new Foo());
        WeakReference<Foo> fooWeakReference1 = new WeakReference<>(new Foo(), new ReferenceQueue<>());

        // 虛引用類型
        PhantomReference<Foo> fooPhantomReference = new PhantomReference<>(new Foo(), new ReferenceQueue<>());
    }
}

那除了我們一直使用的強引用類型之外,其它三種引用類型在什么時候才能使用到呢?

對于軟引用類型,SoftReference類的Javadoc有這樣一句:

Soft references are most often used to implement memory-sensitive caches.

就是說軟引用經(jīng)常被用來實現(xiàn)內(nèi)存敏感型的緩存。其實軟引用就是用來引用那些有用但不是必需的對象,如果堆內(nèi)存夠用就保留著,如果不夠用就清理掉。但是注意不要過度使用軟引用,因為我們不知道這些對象何時會被回收,只能保證的是在發(fā)生OOM之前一定會回收這些對象(如果之前沒被回收的話),而且不同的回收器針對軟可達對象也會有不同的回收策略,如什么情況下才認為堆內(nèi)存不夠用了?堆內(nèi)存不夠用的話是先對堆進行擴容,還是先回收軟可達對象?我在幾個Android手機上進行測試發(fā)現(xiàn),多數(shù)手機的虛擬機(delvik/art)會優(yōu)先回收軟可達對象,如果空閑內(nèi)存仍然不夠再進行堆的擴容。如果把那些你覺得可能會用到的,而實際上很少會用到的對象使用軟引用類型來引用的話,那勢必會造成堆的空閑內(nèi)存減少,那么就可能會引起程序GC次數(shù)的增加或引起不必要的堆的擴容。

我們使用弱引用類型引用某個對象大多數(shù)情況下是為了不影響這個對象的回收。如何實現(xiàn)不影響回收的呢?前面我們說過當對象不是強可達也不是軟可達時,如果引用鏈中存在弱引用,這個對象就是弱可達的,而弱可達的對象會被標記為可回收,并將在本次GC時對它進行回收。所以當我們使用了弱引用類型的話,對象就會可能從強可達狀態(tài)或者軟可達狀態(tài)變成弱可達狀態(tài)(因為引用鏈中存在弱引用類型),而弱可達狀態(tài)的對象會在本次GC時被回收(有例外,后面會提到),所以基本上不會影響該對象的回收。同時我們又可以通過WeakReference.get()來獲取被應(yīng)用的對象,如果獲取到的對象非空,說明對象還沒到弱可達狀態(tài)(仍是強可達或軟可達狀態(tài)),如果獲取到的為空,說明該對象已經(jīng)變成了弱可達狀態(tài),它很快就會被回收或已經(jīng)被回收,所以我們不能再使用它了。

但是這里有一個可能會讓人理解錯誤的地方,弱可達狀態(tài)的對象是不是一定會在本次GC時被回收,或者說WeakReference.get() == null時就一定表明它引用的對象要被回收了嗎?

雖然大部分情況下的確是這樣的,但并不是絕對的。當對象被標記為可回收后,還需要判斷是否需要執(zhí)行該對象的finalize()方法,如果需要則會把該對象放到一個隊列中等待執(zhí)行它的finalize()方法;而如果在執(zhí)行finalize()方法時重新讓外部的某個對象(強)引用到自身,那么這個對象就會從弱可達變成強可達,這樣下次GC時它就不會被回收了(這就是我前面說的存在不可達對象重新變成可達對象的可能)。當然這種情況是很少見的,因為需要執(zhí)行finalize()方法的條件是類重寫了finalize()方法,并且該對象的finialize()方法還沒有被執(zhí)行過。但是最終得到的答案是弱可達狀態(tài)的對象不一定會在下一次GC時被回收

public class WeakReferenceTest {

    private static Foo sFoo;

    private static final class Foo {

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            // 在finalize()中把自身賦值到一個外部變量中
            sFoo = this;
        }
    }

    public static void main(String[] args) {
        WeakReference<Foo> weakReference = new WeakReference<>(new Foo());
        assert weakReference.get() != null;
        new Thread(() -> {

            // 手動觸發(fā)GC和finalization
            Runtime.getRuntime().gc();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new AssertionError();
            }
            System.runFinalization();

            Foo foo = weakReference.get();
            if (foo == null) {
                System.out.println("foo is gone");
            } else {
                System.out.println("foo is still here");
            }

            if (sFoo == null) {
                System.out.println("sFoo is gone");
            } else {
                System.out.println("sFoo is still here");
            }
        }).start();
    }
}
/**
 * 輸出:
 * foo is gone
 * sFoo is still here
 */

虛引用其實挺難讓人理解的,因為它看似沒有什么用。PhantomReference.get()總是返回null,那我要這樣一個引用類型干什么?其實從上面創(chuàng)建幾種引用類型實例的代碼就可以看出來:PhantomReference不像SoftReference和WeakReference,它只有一個接受被引用對象和引用隊列對象的構(gòu)造函數(shù),而SoftReference和WeakReference都可以不傳入引用隊列對象,PhantomReference.get()不是它的重點,重點是它所關(guān)聯(lián)的那個引用隊列。這個引用隊列的作用是當該引用隊列關(guān)聯(lián)的虛引用對象所引用的對象變成虛可達狀態(tài)時,這個虛引用對象本身(PhantomReference對象本身)會被(立即或在隨后的某一時間)放入到這個引用隊列中,這樣我們就可以通過在引用隊列中能否找到某個虛引用對象來判斷它所引用的對象是否已經(jīng)處于虛可達狀態(tài)。

判斷對象是否已經(jīng)處于虛可達狀態(tài)又有什么用呢?看一下PhantomReference類的Javadoc:

Phantom reference objects, which are enqueued after the collector determines that their referents may otherwise be reclaimed.

也就是說當這個虛引用對象被放入到引用隊列中時,它所引用的對象已經(jīng)被回收了。是真正的被回收了,不像弱引用那樣還可以通過finalize()方法來逃脫回收。所以如果我們想判斷某個對象是不是真正的被回收了,最準確的方法是使用PhantomReference,通過它關(guān)聯(lián)的引用隊列中是否存在該虛引用對象來判斷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容