天天看點

阿裡面試: 強引用、軟引用、弱引用、虛引用

阿裡面試: 強引用、軟引用、弱引用、虛引用

我們都知道 JVM 垃圾回收中,GC判斷堆中的對象執行個體或資料是不是垃圾的方法有引用計數法和可達性算法兩種。

無論是通過引用計數算法判斷對象的引用數量,還是通過根搜尋算法判斷對象的引用鍊是否可達,判定對象是否存活都與“引用”有關。

引用

先說說引用,Java中的引用,類似 C 語言中的指針。初學 Java時,我們就知道 Java 資料類型分兩大類,基本類型和引用類型。

基本類型:程式設計語言中内置的最小粒度的資料類型。它包括四大類八種類型:
  • 4種整數類型:byte、short、int、long
  • 2種浮點數類型:float、double
  • 1種字元類型:char
  • 1種布爾類型:boolean
引用類型:引用類型指向一個對象,不是原始值,指向對象的變量是引用變量。在 Java 裡,除了基本類型,其他類型都屬于引用類型,它主要包括:類、接口、數組、枚舉、注解

有了資料類型,JVM對程式資料的管理就規範化了,不同的資料類型,它的存儲形式和位置是不一樣的

怎麼跑偏了,回歸正題,通過引用,可以對堆中的對象進行操作。引用《Java程式設計思想》中的一段話,

”每種程式設計語言都有自己的資料處理方式。有些時候,程式員必須注意将要處理的資料是什麼類型。你是直接操縱元素,還是用某種基于特殊文法的間接表示(例如C/C++裡的指針)來操作對象。所有這些在 Java 裡都得到了簡化,一切都被視為對象。是以,我們可采用一種統一的文法。盡管将一切都“看作”對象,但操縱的辨別符實際是指向一個對象的“引用”(reference)。”

比如:

Person person = new Person("張三");
           

這裡的 person 就是指向Person 執行個體“張三”的引用,我們一般都是通過 person 來操作“張三”執行個體。

在 JDK 1.2 之前,Java 中的引用的定義很傳統:如果 reference 類型的資料中存儲的數值代表的是另外一塊記憶體的起始位址,就稱該 refrence 資料是代表某塊記憶體、某個對象的引用。這種定義很純粹,但是太過狹隘,一個對象在這種定義下隻有被引用或者沒有被引用兩種狀态,對于如何描述一些“食之無味,棄之可惜”的對象就顯得無能為力。

比如我們希望能描述這樣一類對象:當記憶體空間還足夠時,則能保留在記憶體之中;如果記憶體在進行垃圾收集後還是非常緊張,則可以抛棄這些對象。很多系統的緩存功能都符合這樣的應用場景。

在 JDK 1.2 之後,Java 對引用的概念進行了擴充,将引用分為

  • 強引用(Strong Reference)
  • 軟引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虛引用(Phantom Reference)

這四種引用強度依次逐漸減弱。

Java 中引入四種引用的目的是讓程式自己決定對象的生命周期,JVM 是通過垃圾回收器對這四種引用做不同的處理,來實作對象生命周期的改變。

JDK 8中的 UML關系圖

阿裡面試: 強引用、軟引用、弱引用、虛引用

FinalReference 類是包内可見,其他三種引用類型均為 public,可以在應用程式中直接使用。

強引用

在 Java 中最常見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。類似

“Object obj = new Object()”

這類的引用。

當一個對象被強引用變量引用時,它處于可達狀态,是不可能被垃圾回收器回收的,即使該對象永遠不會被用到也不會被回收。

當記憶體不足,JVM 開始垃圾回收,對于強引用的對象,就算是出現了 OOM 也不會對該對象進行回收,打死都不收。是以強引用有時也是造成 Java 記憶體洩露的原因之一。

對于一個普通的對象,如果沒有其他的引用關系,隻要超過了引用的作用域或者顯示地将相應(強)引用指派為 null,一般認為就是可以被垃圾收集器回收。(具體回收時機還要要看垃圾收集政策)。

coding~

public class StrongRefenenceDemo {

    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = o1;
        o1 = null;
        System.gc();
        System.out.println(o1);  //null
        System.out.println(o2);  //java.lang.Object@2503dbd3
    }
}
           

demo 中盡管 o1已經被回收,但是 o2 強引用 o1,一直存在,是以不會被GC回收

軟引用

軟引用是一種相對強引用弱化了一些的引用,需要用

java.lang.ref.SoftReference

類來實作,可以讓對象豁免一些垃圾收集。

軟引用用來描述一些還有用,但并非必需的對象。對于軟引用關聯着的對象,在系統将要發生記憶體溢出異常之前,将會把這些對象列進回收範圍之中并進行第二次回收。如果這次回收還是沒有足夠的記憶體,才會抛出記憶體溢出異常。

對于隻有軟引用的對象來說:當系統記憶體充足時它不會被回收,當系統記憶體不足時它才會被回收。

//VM options: -Xms5m -Xmx5m
public class SoftRefenenceDemo {

    public static void main(String[] args) {
        softRefMemoryEnough();
        System.out.println("------記憶體不夠用的情況------");
        softRefMemoryNotEnough();
    }

    private static void softRefMemoryEnough() {
        Object o1 = new Object();
        SoftReference<Object> s1 = new SoftReference<Object>(o1);
        System.out.println(o1);
        System.out.println(s1.get());

        o1 = null;
        System.gc();

        System.out.println(o1);
        System.out.println(s1.get());
    }

     /**
     * JVM配置`-Xms5m -Xmx5m` ,然後故意new一個一個大對象,使記憶體不足産生 OOM,看軟引用回收情況
     */
    private static void softRefMemoryNotEnough() {
        Object o1 = new Object();
        SoftReference<Object> s1 = new SoftReference<Object>(o1);
        System.out.println(o1);
        System.out.println(s1.get());

        o1 = null;

        byte[] bytes = new byte[10 * 1024 * 1024];

        System.out.println(o1);
        System.out.println(s1.get());
    }
}
           

Output

java.lang.Object@2503dbd3
java.lang.Object@2503dbd3
null
java.lang.Object@2503dbd3
------記憶體不夠用的情況------
java.lang.Object@4b67cf4d
java.lang.Object@4b67cf4d
java.lang.OutOfMemoryError: Java heap space
	at reference.SoftRefenenceDemo.softRefMemoryNotEnough(SoftRefenenceDemo.java:42)
	at reference.SoftRefenenceDemo.main(SoftRefenenceDemo.java:15)
null
null
           

軟引用通常用在對記憶體敏感的程式中,比如高速緩存就有用到軟引用,記憶體夠用的時候就保留,不夠用就回收。

我們看下 Mybatis 緩存類 SoftCache 用到的軟引用

public Object getObject(Object key) {
    Object result = null;
    SoftReference<Object> softReference = (SoftReference)this.delegate.getObject(key);
    if (softReference != null) {
        result = softReference.get();
        if (result == null) {
            this.delegate.removeObject(key);
        } else {
            synchronized(this.hardLinksToAvoidGarbageCollection) {
                this.hardLinksToAvoidGarbageCollection.addFirst(result);
                if (this.hardLinksToAvoidGarbageCollection.size() > this.numberOfHardLinks) {
                    this.hardLinksToAvoidGarbageCollection.removeLast();
                }
            }
        }
    }
    return result;
}
           

弱引用

弱引用也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象隻能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論目前記憶體是否足夠,都會回收掉隻被弱引用關聯的對象。

弱引用需要用

java.lang.ref.WeakReference

類來實作,它比軟引用的生存期更短。

對于隻有軟引用的對象來說,隻要垃圾回收機制一運作,不管 JVM 的記憶體空間是否足夠,都會回收該對象占用的記憶體。

public class WeakReferenceDemo {

    public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference<Object> w1 = new WeakReference<Object>(o1);

        System.out.println(o1);
        System.out.println(w1.get());

        o1 = null;
        System.gc();

        System.out.println(o1);
        System.out.println(w1.get());
    }
}
           
Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.

官方文檔這麼寫的,弱引用常被用來實作規範化映射,JDK 中的 WeakHashMap 就是一個這樣的例子

面試官:既然你都知道弱引用,那能說說 WeakHashMap 嗎
public class WeakHashMapDemo {

    public static void main(String[] args) throws InterruptedException {
        myHashMap();
        myWeakHashMap();
    }

    public static void myHashMap() {
        HashMap<String, String> map = new HashMap<String, String>();
        String key = new String("k1");
        String value = "v1";
        map.put(key, value);
        System.out.println(map);

        key = null;
        System.gc();

        System.out.println(map);
    }

    public static void myWeakHashMap() throws InterruptedException {
        WeakHashMap<String, String> map = new WeakHashMap<String, String>();
        //String key = "weak";
        // 剛開始寫成了上邊的代碼
        //思考一下,寫成上邊那樣會怎麼樣? 那可不是引用了
        String key = new String("weak");
        String value = "map";
        map.put(key, value);
        System.out.println(map);
        //去掉強引用
        key = null;
        System.gc();
        Thread.sleep(1000);
        System.out.println(map);
    }
}
           

我們看下 ThreadLocal  中用到的弱引用

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    //......
}
           

虛引用

虛引用也稱為“幽靈引用”或者“幻影引用”,它是最弱的一種引用關系。

虛引用,顧名思義,就是形同虛設,與其他幾種引用都不太一樣,一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象執行個體。

虛引用需要

java.lang.ref.PhantomReference

來實作。

如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收,它不能單獨使用也不能通過它通路對象,虛引用必須和引用隊列(RefenenceQueue)聯合使用。

虛引用的主要作用是跟蹤對象垃圾回收的狀态。僅僅是提供了一種確定對象被 finalize 以後,做某些事情的機制。

PhantomReference 的 get 方法總是傳回 null,是以無法通路對應的引用對象。其意義在于說明一個對象已經進入 finalization 階段,可以被 GC 回收,用來實作比 finalization 機制更靈活的回收操作。

換句話說,設定虛引用的唯一目的,就是在這個對象被回收器回收的時候收到一個系統通知或者後續添加進一步的處理。

Java 允許使用 finalize() 方法在垃圾收集器将對象從記憶體中清除出去之前做必要的清理工作。

public class PhantomReferenceDemo {

    public static void main(String[] args) throws InterruptedException {
        Object o1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
        PhantomReference<Object> phantomReference = new PhantomReference<Object>(o1,referenceQueue);

        System.out.println(o1);
        System.out.println(referenceQueue.poll());
        System.out.println(phantomReference.get());

        o1 = null;
        System.gc();
        Thread.sleep(3000);

        System.out.println(o1);
        System.out.println(referenceQueue.poll()); //引用隊列中
        System.out.println(phantomReference.get());
    }

}
           
java.lang.Object@4554617c
null
null
null
java.lang.ref.PhantomReference@74a14482
null
           

引用隊列

ReferenceQueue 是用來配合引用工作的,沒有ReferenceQueue 一樣可以運作。

SoftReference、WeakReference、PhantomReference 都有一個可以傳遞 ReferenceQueue 的構造器。

建立引用的時候,可以指定關聯的隊列,當 GC 釋放對象記憶體的時候,會将引用加入到引用隊列。如果程式發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的記憶體被回收之前采取必要的行動,這相當于是一種通知機制。

當關聯的引用隊列中有資料的時候,意味着指向的堆記憶體中的對象被回收。通過這種方式,JVM 允許我們在對象被銷毀後,做一些我們自己想做的事情。

阿裡面試: 強引用、軟引用、弱引用、虛引用

最後,稍微了解下源碼中的實作

Reference源碼(JDK8)

強軟弱虛四種引用,我們有了個大概的認識,我們也知道除了強引用沒有對應的類型表示,是普遍存在的。剩下的三種引用都是

java.lang.ref.Reference

的直接子類。

那就會有疑問了,我們可以通過繼承 Reference,自定義引用類型嗎?
Abstract base class for reference objects. This class defines the operations common to all reference objects. Because reference objects are implemented in close cooperation with the garbage collector, this class may not be subclassed directly.

JDK 官方文檔是這麼說的,

Reference

是所有引用對象的基類。這個類定義了所有引用對象的通用操作。因為引用對象是與垃圾收集器緊密協作而實作的,是以這個類可能不能直接子類化。

Reference 的4種狀态

  • Active:新建立的引用執行個體處于Active狀态,但當GC檢測到該執行個體引用的實際對象的可達性發生某些改變(實際對象處于 GC roots 不可達)後,它的狀态将變化為

    Pending

    或者

    Inactive

    。如果 Reference 注冊了ReferenceQueue,則會切換為

    Pending

    ,并且Reference會加入

    pending-Reference

    連結清單中,如果沒有注冊ReferenceQueue,會切換為

    Inactive

  • Pending:當引用執行個體被放置在pending-Reference 連結清單中時,它處于Pending狀态。此時,該執行個體在等待一個叫Reference-handler的線程将此執行個體進行enqueue操作。如果某個引用執行個體沒有注冊在一個引用隊列中,該執行個體将永遠不會進入Pending狀态
  • Enqueued:在ReferenceQueue隊列中的Reference的狀态,如果Reference從隊列中移除,會進入

    Inactive

    狀态
  • Inactive:一旦某個引用執行個體處于Inactive狀态,它的狀态将不再會發生改變,同時說明該引用執行個體所指向的實際對象一定會被GC所回收
阿裡面試: 強引用、軟引用、弱引用、虛引用

Reference

的構造函數和成員變量

public abstract class Reference<T> {
   //引用指向的對象
   private T referent;    
   // reference被回收後,目前Reference執行個體會被添加到這個隊列中
   volatile ReferenceQueue<? super T> queue;
   //下一個Reference執行個體的引用,Reference執行個體通過此構造單向的連結清單
   volatile Reference next;
   //由transient修飾,基于狀态表示不同連結清單中的下一個待處理的對象,主要是pending-reference清單的下一個元素,通過JVM直接調用指派
   private transient Reference<T> discovered;
   // 等待加入隊列的引用清單,這裡明明是個Reference類型的對象,官方文檔确說是個list?
   //因為GC檢測到某個引用執行個體指向的實際對象不可達後,會将該pending指向該引用執行個體,
   //discovered字段則是用來表示下一個需要被處理的執行個體,是以我們隻要不斷地在處理完目前pending之後,将discovered指向的執行個體賦予給pending即可。是以這個pending就相當于是一個連結清單。
   private static Reference<Object> pending = null;
    
    /* -- Constructors -- */
    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
}
           

Reference 提供了兩個構造器,一個帶引用隊列 ReferenceQueue,一個不帶。

帶 ReferenceQueue 的意義在于我們可以從外部通過對 ReferenceQueue 的操作來了解到引用執行個體所指向的實際對象是否被回收了,同時我們也可以通過 ReferenceQueue  對引用執行個體進行一些額外的操作;但如果我們的引用執行個體在建立時沒有指定一個引用隊列,那我們要想知道實際對象是否被回收,就隻能夠不停地輪詢引用執行個體的get() 方法是否為空了。

值得注意的是虛引用 PhantomReference,由于它的 get() 方法永遠傳回 null,是以它的構造函數必須指定一個引用隊列。

這兩種查詢實際對象是否被回收的方法都有應用,如 WeakHashMap 中就選擇去查詢 queue 的資料,來判定是否有對象将被回收;而 ThreadLocalMap,則采用判斷 get() 是否為 null 來作處理。

執行個體方法(和ReferenceHandler線程不相關的方法)

private static Lock lock = new Lock();
// 擷取持有的referent執行個體
public T get() {
    return this.referent;
}
// 把持有的referent執行個體置為null
public void clear() {
    this.referent = null;
}
// 判斷是否處于enqeued狀态
public boolean isEnqueued() {
    return (this.queue == ReferenceQueue.ENQUEUED);
}
// 入隊參數,同時會把referent置為null
public boolean enqueue() {
    return this.queue.enqueue(this);
}
           

ReferenceHandler線程

通過上文的讨論,我們知道一個Reference執行個體化後狀态為Active,其引用的對象被回收後,垃圾回收器将其加入到

pending-Reference

連結清單,等待加入ReferenceQueue。

ReferenceHandler線程是由

Reference

靜态代碼塊中建立并且運作的線程,它的運作方法中依賴了比較多的本地(native)方法,ReferenceHandler線程的主要功能就pending list中的引用執行個體添加到引用隊列中,并将pending指向下一個引用執行個體。

// 控制垃圾回收器操作與Pending狀态的Reference入隊操作不沖突執行的全局鎖
// 垃圾回收器開始一輪垃圾回收前要擷取此鎖
// 是以所有占用這個鎖的代碼必須盡快完成,不能生成新對象,也不能調用使用者代碼
static private class Lock { }
private static Lock lock = new Lock();

private static class ReferenceHandler extends Thread {

    private static void ensureClassInitialized(Class<?> clazz) {
        try {
            Class.forName(clazz.getName(), true, clazz.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
        }
    }

    static {
        ensureClassInitialized(InterruptedException.class);
        ensureClassInitialized(Cleaner.class);
    }

    ReferenceHandler(ThreadGroup g, String name) {
        super(g, name);
    }

    public void run() {
        while (true) {
            tryHandlePending(true);
        }
    }
}

static boolean tryHandlePending(boolean waitForNotify) {
    Reference<Object> r;
    Cleaner c;
    try {
        synchronized (lock) {
            // 判斷pending-Reference連結清單是否有資料
            if (pending != null) {
                // 如果有Pending Reference,從清單中取出
                r = pending;
                c = r instanceof Cleaner ? (Cleaner) r : null;
                // unlink 'r' from 'pending' chain
                pending = r.discovered;
                r.discovered = null;
            } else {
    // 如果沒有Pending Reference,調用wait等待
                if (waitForNotify) {
                    lock.wait();
                }
                // retry if waited
                return waitForNotify;
            }
        }
    } catch (OutOfMemoryError x) {
        Thread.yield();
        return true;
    } catch (InterruptedException x) {
        return true;
    }

    // Fast path for cleaners
    if (c != null) {
        c.clean();
        return true;
    }

    ReferenceQueue<? super Object> q = r.queue;
    if (q != ReferenceQueue.NULL) q.enqueue(r);
    return true;
}

//ReferenceHandler線程是在Reference的static塊中啟動的
static {
    // ThreadGroup繼承目前執行線程(一般是主線程)的線程組
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn, tgn = tg.getParent());
    // 建立線程執行個體,命名為Reference Handler,配置最高優先級和背景運作(守護線程),然後啟動
    Thread handler = new ReferenceHandler(tg, "Reference Handler");
    // ReferenceHandler線程有最高優先級
    handler.setPriority(Thread.MAX_PRIORITY);
    handler.setDaemon(true);
    handler.start();

    // provide access in SharedSecrets
    SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
        @Override
        public boolean tryHandlePendingReference() {
            return tryHandlePending(false);
        }
    });
}
           

由于ReferenceHandler線程是

Reference

的靜态代碼建立的,是以隻要

Reference

這個父類被初始化,該線程就會建立和運作,由于它是守護線程,除非 JVM 程序終結,否則它會一直在背景運作(注意它的

run()

方法裡面使用了死循環)。

ReferenceQueue源碼

public class ReferenceQueue<T> {

    public ReferenceQueue() { }
 // 内部類Null類繼承自ReferenceQueue,覆寫了enqueue方法傳回false
    private static class Null<S> extends ReferenceQueue<S> {
        boolean enqueue(Reference<? extends S> r) {
            return false;
        }
    }
  // 用于辨別沒有注冊Queue
    static ReferenceQueue<Object> NULL = new Null<>();
    // 用于辨別已經處于對應的Queue中
    static ReferenceQueue<Object> ENQUEUED = new Null<>();

    // 靜态内部類,作為鎖對象
    static private class Lock { };
    /* 互斥鎖,用于同步ReferenceHandler的enqueue和使用者線程操作的remove和poll出隊操作 */
    private Lock lock = new Lock();
    // 引用連結清單的頭節點
    private volatile Reference<? extends T> head = null;
    // 引用隊列長度,入隊則增加1,出隊則減少1
    private long queueLength = 0;

    // 入隊操作,隻會被Reference執行個體調用
    boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
        synchronized (lock) {
   // 如果引用執行個體持有的隊列為ReferenceQueue.NULL或者ReferenceQueue.ENQUEUED則入隊失敗傳回false
            ReferenceQueue<?> queue = r.queue;
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            // 目前引用執行個體已經入隊,那麼它本身持有的引用隊列執行個體置為ReferenceQueue.ENQUEUED
            r.queue = ENQUEUED;
            // 如果連結清單沒有元素,則此引用執行個體直接作為頭節點,否則把前一個引用執行個體作為下一個節點
            r.next = (head == null) ? r : head;
            // 目前執行個體更新為頭節點,也就是每一個新入隊的引用執行個體都是作為頭節點,已有的引用執行個體會作為後繼節點
            head = r;
            // 隊列長度增加1
            queueLength++;
            // 特殊處理FinalReference,VM進行計數
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            // 喚醒所有等待的線程
            lock.notifyAll();
            return true;
        }
    }

    // 引用隊列的poll操作,此方法必須在加鎖情況下調用
    private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head;
        if (r != null) {
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            // 更新next節點為頭節點,如果next節點為自身,說明已經走過一次出隊,則傳回null
            head = (rn == r) ? null : rn;
            r.queue = NULL;
            // 目前頭節點變更為環狀隊列,考慮到FinalReference尚為inactive和避免重複出隊的問題
            r.next = r;
            // 隊列長度減少1
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }

    // 隊列的公有poll操作,主要是加鎖後調用reallyPoll
    public Reference<? extends T> poll() {
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }
// 移除引用隊列中的下一個引用元素,實際上也是依賴于reallyPoll的Object提供的阻塞機制
    public Reference<? extends T> remove(long timeout)
        throws IllegalArgumentException, InterruptedException
    {
        if (timeout < 0) {
            throw new IllegalArgumentException("Negative timeout value");
        }
        synchronized (lock) {
            Reference<? extends T> r = reallyPoll();
            if (r != null) return r;
            long start = (timeout == 0) ? 0 : System.nanoTime();
            for (;;) {
                lock.wait(timeout);
                r = reallyPoll();
                if (r != null) return r;
                if (timeout != 0) {
                    long end = System.nanoTime();
                    timeout -= (end - start) / 1000_000;
                    if (timeout <= 0) return null;
                    start = end;
                }
            }
        }
    }

    public Reference<? extends T> remove() throws InterruptedException {
        return remove(0);
    }

    void forEach(Consumer<? super Reference<? extends T>> action) {
        for (Reference<? extends T> r = head; r != null;) {
            action.accept(r);
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            if (rn == r) {
                if (r.queue == ENQUEUED) {
                    // still enqueued -> we reached end of chain
                    r = null;
                } else {
                    // already dequeued: r.queue == NULL; ->
                    // restart from head when overtaken by queue poller(s)
                    r = head;
                }
            } else {
                // next in chain
                r = rn;
            }
        }
    }
}
           

ReferenceQueue

隻存儲了

Reference

連結清單的頭節點,真正的

Reference

連結清單的所有節點是存儲在

Reference

執行個體本身,通過屬性 next 拼接的,

ReferenceQueue

提供了對

Reference

連結清單的入隊、poll、remove等操作

參考與感謝

https://blog.51cto.com/jaybril/2307819

http://www.kdgregory.com/index.php?page=java.refobj

http://throwable.club/2019/02/16/java-reference/

最後給大家推薦一本Java神書,最近已經更新到第3版了,最近有做活動,大家可以去看看!