Java 中的四種引用

以前咱們提到過 GC,但當 Java 中引用的對象愈來愈多,會致使內存空間不足,最終會產生錯誤 OutOfMemoryError,並讓應用程序終止。那爲何 GC 在此時不能多收集一些對象呢?這就和今天說的引用類型有關了。 git

首先,從 JDK1.2 開始,對象的引用被劃分爲4種級別,從而使程序能更加靈活地控制對象的生命週期。這4種級別由高到低依次爲:強引用軟引用弱引用虛引用github

強引用

強引用(Strong Reference)是使用最廣泛的引用。若是一個對象具備強引用,那麼它永遠不會被 GC。例如:緩存

Object strongReference = new Object();複製代碼

當內存空間不足時,JVM 寧願拋出OutOfMemoryError,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足的問題。ide

若是強引用對象不使用時,須要弱化從而能夠被 GC,例如ArrayList中的clear()方法:this

/**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }複製代碼

顯式地設置強引用對象爲null,或讓其超出對象的生命週期範圍,則垃圾回收器認爲該對象不存在引用,就會回收這個對象。具體何時收集這要取決於具體的垃圾回收器。spa

軟引用

若是一個對象只具備軟引用(Soft Reference),當內存空間充足時,垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。讓咱們來看一個例子具體瞭解一下:線程

String str = new String("abc");
    SoftReference<String> softReference = new SoftReference<>(str);
    String result = softReference.get();複製代碼

讓咱們來看一下get()code

public T get() {
        T o = super.get();
        // timestamp表明上一次軟引用上一次被使用的時間(初始化、get())
        // clock表明上一次GC的時間
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }複製代碼

所以,軟引用在被垃圾回收時,也遵循LRU法則,優先回收最近最少被使用的對象進行回收。cdn

軟引用的使用場景可能是內存敏感的高速緩存。具體來講,就是咱們但願將數據存放到緩存中,這樣能夠快速進行讀取。可是,當 JVM 中內存不夠用時,咱們又不但願緩存數據會佔用到 JVM 的內存。例如配合ReferenceQueue,若是軟引用所引用對象被垃圾回收,JVM 就會把這個軟引用加入到與之關聯的引用隊列中:對象

ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
    String str = new String("abc");
    SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);

    str = null;
    // Notify GC
    System.gc();

    System.out.println(softReference.get()); // abc

    Reference<? extends String> reference = referenceQueue.poll();
    System.out.println(reference); //null複製代碼

可是須要注意的是,若是使用軟引用緩存,有可能致使Full GC增多。

弱引用

若是一個對象只具備弱引用(Weak Reference),其生命週期相比於軟引用更加短暫。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會對它進行回收。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。其使用爲:

String str = new String("abc");
    WeakReference<String> weakReference = new WeakReference<>(str);
    str = weakReference.get();複製代碼

講到弱引用,就不得不提到WeakHashMap。和HashMap相比,當咱們給 JVM 分配的內存不足的時候,HashMap 寧肯拋出 OutOfMemoryError 異常,也不會回收其相應的沒有被引用的對象,而 WeakHashMap 則會回收存儲在其中但有被引用的對象。

WeakHashMap 經過將一些沒有被引用的鍵的值賦值爲 null ,這樣的話就會告知GC去回收這些存儲的值了。假如咱們特意傳入 key 爲 null 的鍵,WeakHashMap 會將鍵設置爲特殊的 Oject,源碼爲:

public V put(K key, V value) {
        // key會被從新賦值
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);

        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
            if (h == e.hash && eq(k, e.get())) {
                V oldValue = e.value;
                if (value != oldValue)
                    e.value = value;
                return oldValue;
            }
        }

        modCount++;
        Entry<K,V> e = tab[i];
        tab[i] = new Entry<>(k, value, queue, h, e);
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }

    /**
     * Value representing null keys inside tables.
     * 特殊的key
     */
    private static final Object NULL_KEY = new Object();

    /**
     * Use NULL_KEY for key if it is null.
     */
    private static Object maskNull(Object key) {
        return (key == null) ? NULL_KEY : key;
    }複製代碼

虛引用

虛引用(PhantomReference),顧名思義,就是形同虛設。與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收。

虛引用主要用來跟蹤對象被垃圾回收器回收的活動。 虛引用與軟引用和弱引用的一個區別在於:

虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。

例如:

String str = new String("abc");
    ReferenceQueue queue = new ReferenceQueue();
    // 建立虛引用,要求必須與一個引用隊列關聯
    PhantomReference pr = new PhantomReference(str, queue);複製代碼

程序能夠經過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要進行垃圾回收。若是程序發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起必要的行動,也能夠理解爲一種回調方法。

總結

Java 中4種引用的級別和強度由高到低依次爲:強引用 -> 軟引用 -> 弱引用 -> 虛引用

經過表格,說明其特性:

引用類型 被垃圾回收的時間 使用場景
生存時間
強引用
歷來不會
對象的通常狀態 JVM中止運行時
軟引用
內存不足時
對象緩存
內存不足時
弱引用
正常垃圾回收時
對象緩存
垃圾回收後終止
虛引用
正常垃圾回收時
跟蹤對象的垃圾回收 垃圾回收後終止

有興趣的話能夠訪問個人博客或者關注個人公衆號、頭條號,說不定會有意外的驚喜。

death00.github.io/

相關文章
相關標籤/搜索