深刻理解JDK中的Reference原理和源碼實現

前提

這篇文章主要基於JDK11的源碼和最近翻看的《深刻理解Java虛擬機-2nd》一書的部份內容,對JDK11中的Reference(引用)作一些總結。值得注意的是,經過筆者對比一下JDK11和JDK8對於java.lang.ref包的相關實現,發現代碼變化比較大,所以本文的源碼分析可能並不適合於JDK11以外的JDK版本java

Reference的簡介和分類

在JDK1.2以前,Java中的引用的定義是十分傳統的:若是reference類型的數據中存儲的數值表明的是另外一塊內存的起始地址,就稱這塊內存表明着一個引用。在這種定義之下,一個對象只有被引用和沒有被引用兩種狀態。算法

實際上,咱們更但願存在這樣的一類對象:當內存空間還足夠的時候,這些對象可以保留在內存空間中;若是當內存空間在進行了垃圾收集以後仍是很是緊張,則能夠拋棄這些對象。基於這種特性,能夠知足不少系統的緩存功能的使用場景。數組

java.lang.ref包是JDK1.2引入的,包結構和類分佈以下:緩存

- java.lang.ref
  - Cleaner.class
  - Finalizer.class
  - FinalizerHistogram.class
  - FinalReference.class
  - PhantomReference.class
  - Reference.class
  - ReferenceQueue.class
  - SoftReference.classs
  - WeakReference.class

引入此包的做用是對引用的概念進行了擴充,將引用分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)四種類型的引用,還有一種比較特殊的引用是析構引用(Final Reference),它是一種特化的虛引用。四種引用的強度按照下面的次序依次減弱:安全

StrongReference > SoftReference > WeakReference > PhantomReference

值得注意的是:app

  • 強引用沒有對應的類型表示,也就是說強引用是廣泛存在的,如Object object = new Object();
  • 軟引用、弱引用和虛引用都是java.lang.ref.Reference的直接子類。
  • 直到JDK11爲止,只存在四種引用,這些引用是由JVM建立,所以直接繼承java.lang.ref.Reference建立自定義的引用類型是無效的,可是能夠直接繼承已經存在的引用類型,如java.lang.ref.Cleaner就是繼承自java.lang.ref.PhantomReference
  • 特殊的java.lang.ref.Reference的子類java.lang.ref.FinalReferenceObject#finalize()有關,java.lang.ref.Finalizerjava.lang.ref.FinalReference子類,下文會詳細分析這些內容。

Reference

Reference就是引用,對JVM的垃圾收集活動敏感(固然,強引用可能對垃圾收集活動是不敏感的),Reference的繼承關係或者實現是由JDK定製,引用實例是由JVM建立,因此自行繼承Reference實現自定義的引用類型是無心義的,可是能夠繼承已經存在的引用類型,如SoftReference等。Reference類文件的註釋也比較簡短,可是方法和變量的註釋十分詳細,特別是用圖表代表了狀態躍遷的過程,這裏先看類文件頭註釋:ide

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.函數

翻譯一下大意是:Reference是全部引用對象的基類。這個類定義了全部引用對象的通用操做。由於引用對象是與垃圾收集器緊密協做而實現的,因此這個類可能不能直接子類化。工具

Reference的狀態集合

Reference源碼中並不存在一個成員變量用於描述Reference的狀態,它是經過組合判斷referent、discovered、queue、next成員的存在性或者順序"拼湊出"對應的狀態,註釋中描述以下:oop

一個引用對象能夠同時存在兩種狀態:
- 第一組狀態:"active", "pending", or "inactive"
- 第二組狀態:"registered", "enqueued", "dequeued", or "unregistered"

Active:

當前引用實例處於Active狀態,會收到垃圾收集器的特殊處理。在垃圾收集器檢測到referent的可達性已更改成適當狀態以後的某個時間,垃圾收集器會"通知"當前引用實例改變其狀態爲"pending"或者"inactive"。此時的判斷條件是:referent != null; discovered = null或者實例位於GC的discovered列表中。

Pending:

當前的引用實例是pending-Reference列表的一個元素,等待被ReferenceHandler線程處理。pending-Reference列表經過應用實例的discovered字段進行關聯。此時的判斷條件是:referent = null; discovered = pending-Reference列表中的下一個元素

Inactive:

當前的引用實例處於非Active和非Pending狀態。此時的判斷條件是:referent = null (同時discovered = null)

Registered:

當前的引用實例建立的時候關聯到一個引用隊列實例,可是引用實例暫未加入到隊列中。此時的判斷條件是:queue = 傳入的ReferenceQueue實例

Enqueued:

當前的引用實例已經添加到和它關聯的引用隊列中可是還沒有移除(remove),也就是調用了ReferenceQueue.enqueued()後的Reference實例就會處於這個狀態。此時的判斷條件是:queue = ReferenceQueue.ENQUEUE; next = 引用列表中的下一個引用實例,或者若是當前引用實例是引用列表中的最後一個元素,則它會進入Inactive狀態

Dequeued:

當前的引用實例曾經添加到和它關聯的引用隊列中而且已經移除(remove)。此時的判斷條件是:queue = ReferenceQueue.NULL; next = 當前的引用實例

Unregistered:

當前的引用實例不存在關聯的引用隊列,也就是建立引用實例的時候傳入的queue爲null。此時的判斷條件是:queue = ReferenceQueue.NULL

狀態躍遷的時序圖以下:

* Initial states:
     *   [active/registered]
     *   [active/unregistered] [1]
     *
     * Transitions:
     *                            clear
     *   [active/registered]     ------->   [inactive/registered]
     *          |                                 |
     *          |                                 | enqueue [2]
     *          | GC              enqueue [2]     |
     *          |                -----------------|
     *          |                                 |
     *          v                                 |
     *   [pending/registered]    ---              v
     *          |                   | ReferenceHandler
     *          | enqueue [2]       |--->   [inactive/enqueued]
     *          v                   |             |
     *   [pending/enqueued]      ---              |
     *          |                                 | poll/remove
     *          | poll/remove                     |
     *          |                                 |
     *          v            ReferenceHandler     v
     *   [pending/dequeued]      ------>    [inactive/dequeued]
     *
     *
     *                           clear/enqueue/GC [3]
     *   [active/unregistered]   ------
     *          |                      |
     *          | GC                   |
     *          |                      |--> [inactive/unregistered]
     *          v                      |
     *   [pending/unregistered]  ------
     *                           ReferenceHandler
     *
     * Terminal states:
     *   [inactive/dequeued]
     *   [inactive/unregistered]
     *
     * Unreachable states (because enqueue also clears):
     *   [active/enqeued]
     *   [active/dequeued]
     *
     * [1] Unregistered is not permitted for FinalReferences.
     *
     * [2] These transitions are not possible for FinalReferences, making
     * [pending/enqueued] and [pending/dequeued] unreachable, and
     * [inactive/registered] terminal.
     *
     * [3] The garbage collector may directly transition a Reference
     * from [active/unregistered] to [inactive/unregistered],
     * bypassing the pending-Reference list.

註釋中還強調了幾點:

  • 初始化狀態:[active/registered][active/unregistered](這種狀況只限於FinalReferences)
  • 終結狀態:[inactive/dequeued][inactive/unregistered]
  • 不可能出現的狀態:[active/enqeued][active/dequeued]

上面的圖看起來可能比較抽象,ReferenceHandler實際上是Reference中靜態代碼塊中初始化的線程實例,主要做用是:處理pending狀態的引用實例,使它們入隊列並走向[inactive/dequeued]狀態。另外,上面的線框圖是分兩部分,其中上半部分是使用了ReferenceQueue,後半部分是沒有使用ReferenceQueue(或者說使用了ReferenceQueue.NULL)。這裏嘗試用PPT畫一下簡化的狀態躍遷圖:

Reference源碼分析

先看Reference的構造函數和成員變量:

public abstract class Reference<T> {
   private T referent;
   volatile ReferenceQueue<? super T> queue;
   volatile Reference next;
   private transient Reference<T> discovered;

   Reference(T referent) {
        this(referent, null);
   }

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

構造描述

構造函數依賴於一個泛型的referent成員以及一個ReferenceQueue<? super T>的隊列,若是ReferenceQueue實例爲null,則使用ReferenceQueue.NULL

成員變量描述

  • referent:Reference保存的引用指向的對象,下面直接稱爲referent。
// GC特殊處理的對象
private T referent;         /* Treated specially by GC */
  • queue:Reference對象關聯的隊列,也就是引用隊列,對象若是即將被垃圾收集器回收,此隊列做爲通知的回調隊列,也就是當Reference實例持有的對象referent要被回收的時候,Reference實例會被放入引用隊列,那麼程序執行的時候能夠從引用隊列獲得或者監控相應的Reference實例。
/* The queue this reference gets enqueued to by GC notification or by
     * calling enqueue().
     *
     * When registered: the queue with which this reference is registered.
     *        enqueued: ReferenceQueue.ENQUEUE
     *        dequeued: ReferenceQueue.NULL
     *    unregistered: ReferenceQueue.NULL
     */
    volatile ReferenceQueue<? super T> queue;
  • next:下一個Reference實例的引用,Reference實例經過此構造單向的鏈表。
/* The link in a ReferenceQueue's list of Reference objects.
     *
     * When registered: null
     *        enqueued: next element in queue (or this if last)
     *        dequeued: this (marking FinalReferences as inactive)
     *    unregistered: null
     */
    @SuppressWarnings("rawtypes")
    volatile Reference next;
  • discovered:注意這個屬性由transient修飾,基於狀態表示不一樣鏈表中的下一個待處理的對象,主要是pending-reference列表的下一個元素,經過JVM直接調用賦值。
/* When active:  next element in a discovered reference list maintained by GC (or this if last)
*     pending:   next element in the pending list (or null if last)
*     otherwise:   NULL
*/
transient private Reference<T> discovered;  /* used by VM */

實例方法(和ReferenceHandler線程不相關的方法)

// 獲取持有的referent實例
@HotSpotIntrinsicCandidate
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() {
     this.referent = null;
     return this.queue.enqueue(this);
}

// 覆蓋clone方法而且拋出異常,也就是禁止clone
@Override
protected Object clone() throws CloneNotSupportedException {
     throw new CloneNotSupportedException();
}

// 確保給定的引用實例是強可達的
@ForceInline
public static void reachabilityFence(Object ref) {
}

ReferenceHandler線程

ReferenceHandler線程是由Reference靜態代碼塊中創建而且運行的線程,它的運行方法中依賴了比較多的本地(native)方法,ReferenceHandler線程的主要功能是處理pending鏈表中的引用對象:

// ReferenceHandler直接繼承於Thread覆蓋了run方法
    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 {
            // 確保Cleaner這個類已經初始化
            // pre-load and initialize Cleaner class so that we don't
            // get into trouble later in the run loop if there's
            // memory shortage while loading/initializing it lazily.
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, null, name, 0, false);
        }
        
        // 注意run方法是一個死循環執行processPendingReferences
        public void run() {
            while (true) {
                processPendingReferences();
            }
        }
    }

    /* 原子獲取(後)而且清理VM中的pending引用鏈表
     * Atomically get and clear (set to null) the VM's pending-Reference list.
     */
    private static native Reference<Object> getAndClearReferencePendingList();

    /* 檢驗VM中的pending引用對象鏈表是否有剩餘元素
     * Test whether the VM's pending-Reference list contains any entries.
     */
    private static native boolean hasReferencePendingList();

    /* 等待直到pending引用對象鏈表不爲null,此方法阻塞的具體實現又VM實現
     * Wait until the VM's pending-Reference list may be non-null.
     */
    private static native void waitForReferencePendingList();

    // 鎖對象,用於控制等待pending對象時候的加鎖和開始處理這些對象時候的解鎖
    private static final Object processPendingLock = new Object();
    // 正在處理pending對象的時候,這個變量會更新爲true,處理完畢或者初始化狀態爲false,用於避免重複處理或者重複等待
    private static boolean processPendingActive = false;

    // 這個是死循環中的核心方法,功能是處理pending鏈表中的引用元素
    private static void processPendingReferences() {
        // Only the singleton reference processing thread calls
        // waitForReferencePendingList() and getAndClearReferencePendingList().
        // These are separate operations to avoid a race with other threads
        // that are calling waitForReferenceProcessing().
        // (1)等待
        waitForReferencePendingList();
        Reference<Object> pendingList;
        synchronized (processPendingLock) {
            // (2)獲取並清理,標記處理中狀態
            pendingList = getAndClearReferencePendingList();
            processPendingActive = true;
        }
        // (3)經過discovered(下一個元素)遍歷pending鏈表進行處理
        while (pendingList != null) {
            Reference<Object> ref = pendingList;
            pendingList = ref.discovered;
            ref.discovered = null;
            // 若是是Cleaner類型執行執行clean方法而且對鎖對象processPendingLock進行喚醒全部阻塞的線程
            if (ref instanceof Cleaner) {
                ((Cleaner)ref).clean();
                // Notify any waiters that progress has been made.
                // This improves latency for nio.Bits waiters, which
                // are the only important ones.
                synchronized (processPendingLock) {
                    processPendingLock.notifyAll();
                }
            } else {
                // 非Cleaner類型而且引用隊列不爲ReferenceQueue.NULL則進行入隊操做
                ReferenceQueue<? super Object> q = ref.queue;
                if (q != ReferenceQueue.NULL) q.enqueue(ref);
            }
        }
        // (4)當次循環結束以前再次喚醒鎖對象processPendingLock上阻塞的全部線程
        // Notify any waiters of completion of current round.
        synchronized (processPendingLock) {
            processPendingActive = false;
            processPendingLock.notifyAll();
        }
    }

ReferenceHandler線程啓動的靜態代碼塊以下:

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");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
        // 注意這裏覆蓋了全局的jdk.internal.misc.JavaLangRefAccess實現
        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean waitForReferenceProcessing()
                throws InterruptedException{
                return Reference.waitForReferenceProcessing();
            }

            @Override
            public void runFinalization() {
                Finalizer.runFinalization();
            }
        });
    }

    // 若是正在處理pending鏈表中的引用對象或者監測到VM中的pending鏈表中還有剩餘元素則基於鎖對象processPendingLock進行等待
    private static boolean waitForReferenceProcessing()
        throws InterruptedException{
        synchronized (processPendingLock) {
            if (processPendingActive || hasReferencePendingList()) {
                // Wait for progress, not necessarily completion.
                processPendingLock.wait();
                return true;
            } else {
                return false;
            }
        }
    }

因爲ReferenceHandler線程是Reference的靜態代碼建立的,因此只要Reference這個父類被初始化,該線程就會建立和運行,因爲它是守護線程,除非JVM進程終結,不然它會一直在後臺運行(注意它的run()方法裏面使用了死循環)。

ReferenceQueue

JDK中對ReferenceQueue的文檔描述是比較少的,類文件只有一句簡單的註釋:

Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.

翻譯一下大意爲:引用隊列,垃圾收集器在檢測到適當的可達性更改後將已註冊的引用對象追加到該隊列。

從源碼上看,實際上ReferenceQueue只是名義上的引用隊列,它只保存了Reference鏈表的頭(head)節點,而且提供了出隊、入隊和移除等操做,而Reference實際上自己提供單向鏈表的功能,也就是Reference經過成員屬性next構建單向鏈表,而鏈表的操做是委託給ReferenceQueue完成,這裏的邏輯有點繞ReferenceQueue的源碼比較少,這裏全量貼出標註一下注釋:

public class ReferenceQueue<T> {

    public ReferenceQueue() { }
    
    // 內部類Null類繼承自ReferenceQueue,覆蓋了enqueue方法返回false
    private static class Null extends ReferenceQueue<Object> {
        boolean enqueue(Reference<?> r) {
            return false;
        }
    }
    
    // ReferenceQueue.NULL和ReferenceQueue.ENQUEUED都是內部類Null的新實例
    static final ReferenceQueue<Object> NULL = new Null();
    static final ReferenceQueue<Object> ENQUEUED = new Null();
    
    // 靜態內部類,做爲鎖對象
    private static class Lock { };
    // 鎖實例
    private final Lock lock = new Lock();
    // 引用鏈表的頭節點
    private volatile Reference<? extends T> head;
    // 引用隊列長度,入隊則增長1,出隊則減小1
    private long queueLength = 0;  

    // 入隊操做,只會被Reference實例調用
    boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
        // 加鎖
        synchronized (lock) {
            // Check that since getting the lock this reference hasn't already been
            // enqueued (and even then removed)
            // 若是引用實例持有的隊列爲ReferenceQueue.NULL或者ReferenceQueue.ENQUEUED則入隊失敗返回false
            ReferenceQueue<?> queue = r.queue;
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            // Self-loop end, so if a FinalReference it remains inactive.
            // 若是鏈表沒有元素,則此引用實例直接做爲頭節點,不然把前一個引用實例做爲下一個節點
            r.next = (head == null) ? r : head;
            // 當前實例更新爲頭節點,也就是每個新入隊的引用實例都是做爲頭節點,已有的引用實例會做爲後繼節點
            head = r;
            // 隊列長度增長1
            queueLength++;
            // Update r.queue *after* adding to list, to avoid race
            // with concurrent enqueued checks and fast-path poll().
            // Volatiles ensure ordering.
            // 當前引用實例已經入隊,那麼它自己持有的引用隊列實例置爲ReferenceQueue.ENQUEUED
            r.queue = ENQUEUED;
            // 特殊處理FinalReference,VM進行計數
            if (r instanceof FinalReference) {
                VM.addFinalRefCount(1);
            }
            // 喚醒全部等待的線程
            lock.notifyAll();
            return true;
        }
    }

    // 引用隊列的poll操做,此方法必須在加鎖狀況下調用
    private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head;
        if (r != null) {
            r.queue = NULL;
            // Update r.queue *before* removing from list, to avoid
            // race with concurrent enqueued checks and fast-path
            // poll().  Volatiles ensure ordering.
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            // Handle self-looped next as end of list designator.
            // 更新next節點爲頭節點,若是next節點爲自身,說明已經走過一次出隊,則返回null
            head = (rn == r) ? null : rn;
            // Self-loop next rather than setting to null, so if a
            // FinalReference it remains inactive.
            // 當前頭節點變動爲環狀隊列,考慮到FinalReference尚爲inactive和避免重複出隊的問題
            r.next = r;
            // 隊列長度減小1
            queueLength--;
            // 特殊處理FinalReference,VM進行計數
            if (r instanceof FinalReference) {
                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;
                }
            }
        }
    }
    
    // remove,超時時間爲0,實際上就是lock.wait(0)就是永久阻塞直至喚醒
    public Reference<? extends T> remove() throws InterruptedException {
        return remove(0);
    } 

    // foreach
    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等操做。

判斷對象的可達性和對象是否存活

判斷對象的可達性和對象是否存活是兩個比較困難的問題,筆者C語言學得比較爛,不然會重點翻看一下JVM的實現,目前只能參考一些資料來講明這個問題。

可達性算法

主流商用語言包括Java都是使用可達性分析(Reachability Analysis)算法來斷定對象是否存活的。這個算法的基本思路是經過一系列的稱爲"GC Roots"(GC根集)的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC根集沒有任何引用鏈相連(從圖論的角度看,也就是從GC根集到這個對象是不可達的)時,則證實此對象是不可用的。不可用的對象"有機會"被斷定爲能夠回收的對象。

在Java語言中,能夠做爲GC根集的對象包括下面幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象。
  • 方法區中常量引用的對象(在JDK1.8以後不存在方法區,也就是有多是metaspace中常量引用的對象)。
  • 本地方法棧中JNI(即通常常說的Native方法)引用的對象。

finalize函數

即便在可達性分析算法中斷定爲不可達的對象,也並不是必定會斷定爲能夠被回收的"死亡"對象。一個對象斷定爲"死亡"至少須要經歷兩次標記的過程。

第一次標記:若是對象在進行可達性分析後發現沒有與GC Roots相鏈接的引用鏈,那麼它將會被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。JVM會把如下兩種狀況認爲對象沒有必要執行finalize()方法:

  • 對象沒有覆蓋繼承自Object類的finalize()方法。
  • 對象的finalize()方法已經被JVM調用過。

若是一個對象被斷定爲有必要執行finalize()方法,那麼這個對象將會被放置在一個叫F-Queue的隊列之中,而且稍後由一個優先級低的Finalizer線程去取該隊列的元素,"嘗試執行"元素的finalize()方法。這裏之因此叫嘗試執行是由於JVM會保證觸發知足條件的對象的finalize()方法,可是並不承諾會等待它執行結束,這是由於:若是一個對象在執行finalize()方法耗時較長,甚至發生了死循環,將會致使F-Queue的隊列中的其餘元素永遠處於等待狀態,極端狀況下有可能致使整個內存回收系統崩潰

finalize()方法是對象逃脫死亡命運的最後一次機會,由於稍後的GC將會對F-Queue隊列中的對象進行第二次小規模的標記,若是對象在finalize()方法執行過程當中成功拯救本身--也就是對象自身從新與引用鏈的任何一個對象創建關聯便可,最多見的就是把自身(this關鍵字)賦值給某個類變量或者對象的成員屬性,那麼在第二次小規模的標記時候將會把"自我拯救"成功的對象移出"即將回收"的集合。若是對象在finalize()方法執行過程當中沒有"逃逸",那麼它最終就會被回收。參考《深刻理解Java虛擬機-2nd》的"對象自我拯救的例子":

public class FinalizeEscapeGc {

    private static FinalizeEscapeGc SAVE_HOOK = null;

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

    public static void main(String[] args) throws Exception {
        SAVE_HOOK = new FinalizeEscapeGc();

        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("No,I am not alive :(");
        }
                // 下面的這段代碼和上面的一致
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("No,I am not alive :(");
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("FinalizeEscapeGc finalize invoke...");
        FinalizeEscapeGc.SAVE_HOOK = this;
    }
}
// 輸出結果
FinalizeEscapeGc finalize invoke...
Yes,I am still alive :)
No,I am not alive :(

注意:

  • finalize()方法的錯誤使用有多是內存回收系統崩潰的根源,通常狀況下謹慎思考是否真的須要覆蓋此方法。
  • 任意一個對象只能經過finalize()方法自我拯救一次。

Finalizer守護線程

前面提到的Finalizer守護線程和F-Queue隊列其實在JDK中有具體的實現類java.lang.ref.FinalizerF-Queue隊列只是《深刻理解Java虛擬機-2nd》中的一個名詞描述,實際上筆者沒有找到相關的資料,這裏咱們經過分析JDK和JVM相關的源碼去理解這個F-Queue隊列吧。先看java.lang.ref.Finalizer的源碼,代碼比較少全量貼出:

final class Finalizer extends FinalReference<Object> { /* Package-private; must be in
                                                          same package as the Reference
                                                          class */
    // Finalizer關聯的ReferenceQueue,其實Finalizer是一個特殊的Reference實現
    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();

    /** Head of doubly linked list of Finalizers awaiting finalization. */
    // 等待finalization的全部Finalizer實例鏈表的頭節點,這裏稱此鏈表爲unfinalized鏈表
    private static Finalizer unfinalized = null;

    /** Lock guarding access to unfinalized list. */
    // unfinalized鏈表的鎖,靜態final,全局的鎖實例
    private static final Object lock = new Object();
    
    // 中間變量,分別記錄unfinalized鏈表中當前執行元素的下一個節點和前一個節點
    private Finalizer next, prev;

    private Finalizer(Object finalizee) {
        super(finalizee, queue);
        // push onto unfinalized
        // 這裏主要是更新unfinalized鏈表的頭節點,新增的元素老是會變成頭節點
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

    static ReferenceQueue<Object> getQueue() {
        return queue;
    }

    /* Invoked by VM */ 這個方法由JVM激活,也就是鏈表的元素入隊是由JVM控制的,見下文分析
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }

    private void runFinalizer(JavaLangAccess jla) {
        synchronized (lock) {
            // 當前元素已經處理過,直接返回
            if (this.next == this)      // already finalized
                return;
            // unlink from unfinalized
            // 下面的邏輯是當前須要執行的元素從鏈表中移除,而且更新prev和next的值,至關於重建鏈表的部分節點
            if (unfinalized == this)
                unfinalized = this.next;
            else
                this.prev.next = this.next;
            if (this.next != null)
                this.next.prev = this.prev;
            this.prev = null;
            this.next = this;           // mark as finalized
        }

        try {
            // 獲取對象執行一次finalize方法
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                jla.invokeFinalize(finalizee);

                // Clear stack slot containing this variable, to decrease
                // the chances of false retention with a conservative GC
                // 清空變量引用從而減小保守GC致使變量保留的可能性
                finalizee = null;
            }
        } catch (Throwable x) { }
        // 執行完畢會作一次狀況防止重複執行
        super.clear();
    }

    /* Create a privileged secondary finalizer thread in the system thread
     * group for the given Runnable, and wait for it to complete.
     *
     * This method is used by runFinalization.
     *
     * It could have been implemented by offloading the work to the
     * regular finalizer thread and waiting for that thread to finish.
     * The advantage of creating a fresh thread, however, is that it insulates
     * invokers of that method from a stalled or deadlocked finalizer thread.
     */
    // 這裏其實不用畏懼註釋太多,它只是一個候選方法,新建一個線程直接調用包裹在Runnable的runFinalization方法,主要是提供給主動調用的上層方法調用的
    private static void forkSecondaryFinalizer(final Runnable proc) {
        AccessController.doPrivileged(
            new PrivilegedAction<>() {
                public Void run() {
                    ThreadGroup tg = Thread.currentThread().getThreadGroup();
                    for (ThreadGroup tgn = tg;
                         tgn != null;
                         tg = tgn, tgn = tg.getParent());
                    Thread sft = new Thread(tg, proc, "Secondary finalizer", 0, false);
                    sft.start();
                    try {
                        sft.join();
                    } catch (InterruptedException x) {
                        Thread.currentThread().interrupt();
                    }
                    return null;
                }});
    }

    /* Called by Runtime.runFinalization() */
    // 這個方法是給Runtime.runFinalization()委託調用的,其實就是主動取出queue的元素強制調用其finalize方法
    static void runFinalization() {
        if (VM.initLevel() == 0) {
            return;
        }
        forkSecondaryFinalizer(new Runnable() {
            private volatile boolean running;
            public void run() {
                // in case of recursive call to run()
                if (running)
                    return;
                final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
                running = true;
                for (Finalizer f; (f = (Finalizer)queue.poll()) != null;)
                    f.runFinalizer(jla);
            }
        });
    }
    
    // 真正的Finalizer線程
    private static class FinalizerThread extends Thread {
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, null, "Finalizer", 0, false);
        }
        public void run() {
            // in case of recursive call to run()
            if (running)
                return;

            // Finalizer thread starts before System.initializeSystemClass
            // is called.  Wait until JavaLangAccess is available
            while (VM.initLevel() == 0) {
                // delay until VM completes initialization
                try {
                    VM.awaitInitLevel(1);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            // 注意這裏是死循環
            for (;;) {
                try {
                    // 注意這裏是調用`Reference#remove()`的永久阻塞版本,只有`Reference#enqueue()`被調用纔會解除阻塞
                    // `Reference#remove()`解除阻塞說明元素已經完成入隊,由ReferenceHandler線程完成
                    Finalizer f = (Finalizer)queue.remove();
                    // 實際上就是調用對象的finalize方法
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }

    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        // 靜態代碼塊中聲明線程,優先級是最高優先級-2,守護線程,實際上這裏優先級不必定會生效
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }
}

上面的註釋已經很明顯標註出來,這裏小結一下內容。

  • FinalizerFinalReference的子類,而FinalReferenceReference的實現,因此它的工做原理和其餘引用相似,對象的狀態更變和由ReferenceHandler線程密切相關。
  • Finalizer內部維護了一個鏈表,每當JVM調用靜態註冊方法就會新建一個Finalizer實例加入到鏈表的頭節點中,頭節點元素爲unfinalized,這裏稱此鏈表爲unfinalized鏈表。
  • Finalizer線程由Finalizer靜態代碼塊構建而且運行,它是守護線程,優先級是最高優先級-2,它的做用就是提取unfinalized鏈表的元素而且執行元素對象的finalize()方法,過程當中還會涉及到線程的阻塞、喚醒,以及unfinalized鏈表的重建等工做。

因爲靜態方法Finalizer#register(Object finalizee)是由JVM調用的,因此咱們必需要分析一些JVM的源碼,參考的是OpenJDK主分支的代碼,文件是instanceKlass.cpp

instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) {
  if (TraceFinalizerRegistration) {
    tty->print("Registered ");
    i->print_value_on(tty);
    tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", p2i(i));
  }
  instanceHandle h_i(THREAD, i);
  // Pass the handle as argument, JavaCalls::call expects oop as jobjects
  JavaValue result(T_VOID);
  JavaCallArguments args(h_i);
  // 這裏Universe::finalizer_register_method()獲取到的就是Finalizer#register方法句柄
  methodHandle mh (THREAD, Universe::finalizer_register_method());
  JavaCalls::call(&result, mh, &args, CHECK_NULL);
  return h_i();
}

最後調用的是javaCalls.cpp

void JavaCalls::call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
  // Check if we need to wrap a potential OS exception handler around thread
  // This is used for e.g. Win32 structured exception handlers
  assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls");
  // Need to wrap each and every time, since there might be native code down the
  // stack that has installed its own exception handlers
  os::os_exception_wrapper(call_helper, result, method, args, THREAD);
}

簡單來看就是把建立對象過程當中,若是有必要註冊Finalizer(通常是覆蓋了finalize()方法),則基於當前線程經過Finalizer#register(Object finalizee)把當前新建的實例註冊到Finalizer自身維護的鏈表中(若是沒理解錯,所謂的F-Queue就是這個鏈表了),等待後臺Finalizer線程輪詢而且執行鏈表中對象的finalize()方法。

各種引用以及它們的使用場景

這裏提到的各種引用目前就是四種:強引用(StrongReference)、軟引用(SoftReference)、弱引用(WeakReference)和虛引用(PhantomReference)。其實還有特殊的引用類型FinalReference,它是包私有的,而且只有一個子類型Finalizer

StrongReference

StrongReference也就是強引用,它是使用最廣泛的一種引用,java.lang.ref包下沒有強引用對應的類型。一個比較明確的強引用定義就是:全部和GC Root之間存在引用鏈的對象都具有強引用。舉個簡單例子:形如Object o = new Object();在方法體中使用new關鍵字聲明的對象通常就是強引用。若是一個對象具有強引用,垃圾回收器毫不會回收它。當內存空間不足,JVM寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會出現回收具備強引用的對象來解決內存不足的狀況。固然,若是有共享的成員變量在方法退出以前置爲null,至關於斷絕成員變量和GC Root的引用鏈,在合適的時機是有利於GC後具有強引用的對象的回收,例如:

private Object shareValue = XXX;

public void methodA(){
    //do something
    shareValue = null;
}

後來有人過分信奉相似上面的這個實踐,出現了一條比較詭異的編碼實踐:強引用使用完畢後都要置爲null方便對象回收。可是實際上,這個實踐並非在任何場景都是合理的。

SoftReference

SoftReference也就是軟引用,它是用來描述一些"還有用可是非必須"的對象。對於軟引用關聯着的對象,在JVM應用即將發生內存溢出異常以前,將會把這些軟引用關聯的對象列進去回收對象範圍之中進行第二次回收。若是此次回收以後仍是沒有足夠的內存,纔會拋出內存溢出異常。簡單來講就是:

  • 若是內存空間足夠,垃圾回收器就不會回收軟引用關聯着的對象。
  • 若是內存空間不足,垃圾回收器在將要拋出內存溢出異常以前會回收軟引用關聯着的對象。

舉個簡單的使用例子:

// VM參數:-Xmx4m -Xms4m
public class SoftReferenceMain {

    public static void main(String[] args) throws Exception {
        ReferenceQueue<SoftReferenceObject> queue = new ReferenceQueue<>();
        SoftReferenceObject object = new SoftReferenceObject();
        SoftReference<SoftReferenceObject> reference = new SoftReference<>(object, queue);
        object = null;
        System.gc();
        Thread.sleep(500);
        System.out.println(reference.get());
    }

    private static class SoftReferenceObject {

        int[] array = new int[120_000];

        @Override
        public String toString() {
            return "SoftReferenceObject";
        }
    }
}
// 運行後輸出結果
null

上面的例子故意把JVM的啓動的最大Heap內存和初始Heap內存設置爲4MB,使用這個對象初始化一個比較大的整型數組而且關係到一個軟引用對象中,GC以後,發現軟引用關聯的對象被回收了。

WeakReference

WeakReference也就是弱引用,弱引用和軟引用相似,它是用來描述"非必須"的對象的,它的強度比軟引用要更弱一些。被弱引用關聯的對象只能生存到下一次垃圾收集發生以前,簡言之就是:一旦發生GC一定回收被弱引用關聯的對象,無論當前的內存是否足夠。

舉個例子:

public class WeakReferenceMain {

    public static void main(String[] args) throws Exception {
        ReferenceQueue<WeakReferenceObject> queue = new ReferenceQueue<>();
        WeakReferenceObject object = new WeakReferenceObject();
        System.out.println(object);
        WeakReference<WeakReferenceObject> reference = new WeakReference<>(object, queue);
        object = null;
        System.gc();
        Thread.sleep(500);
        System.out.println(reference.get());
    }

    private static class WeakReferenceObject {

        @Override
        public String toString() {
            return "WeakReferenceObject";
        }
    }
}
// 運行後輸出結果
WeakReferenceObject
null

上面的例子中沒有設定JVM的堆內存,所以不存在內存不足的狀況,可見弱引用關聯的對象在GC以後被回收了。弱引用適合用來作對內存敏感的緩存,很經常使用的WeakHashMap就是基於弱引用實現的。

PhantomReference

PhantomReference也就是虛引用,也叫幽靈引用或者幻影引用,它是全部引用類型中最弱的一種。一個對象是否關聯到虛引用,徹底不會影響該對象的生命週期,也沒法經過虛引用來獲取一個對象的實例(PhantomReference覆蓋了Reference#get()而且老是返回null)。爲對象設置一個虛引用的惟一目的是:能在此對象被垃圾收集器回收的時候收到一個系統通知PhantomReference有兩個比較經常使用的子類是java.lang.ref.Cleanerjdk.internal.ref.Cleaner,其中前者提供的功能是開發者用於在引用對象回收的時候觸發一個動做(java.lang.ref.Cleaner$Cleanable),後者用於DirectByteBuffer對象回收的時候對於堆外內存的回收,能夠翻看前面描述java.lang.ref.Reference#processPendingReferences()源碼的時候,ReferenceHandler線程會對pending鏈表中的jdk.internal.ref.Cleaner類型引用對象調用其clean()方法。PhantomReference自己使用場景比較少,這裏舉一下java.lang.ref.Cleaner註釋中的例子:

public class PhantomReferenceMain {

    public static void main(String[] args) throws Exception {
        try (CleaningExample o = new CleaningExample(11)){

        }
        CleaningExample o2 = new CleaningExample(22);
        System.gc();
        Thread.sleep(300);
    }

}

class CleaningExample implements AutoCloseable {

    private Cleaner cleaner = Cleaner.create();
    private final State state;
    private final Cleaner.Cleanable cleanable;

    public CleaningExample(int s) {
        state = new State(s);
        cleanable = cleaner.register(this, state);
    }

    class State implements Runnable {

        private final int s;

        public State(int s) {
            this.s = s;
        }

        @Override
        public void run() {
            System.out.println("State runnable in action.State value = " + s);
        }
    }

    @Override
    public void close() throws Exception {
        cleanable.clean();
    }
}

實際上,沙面的代碼執行完畢只會輸出"State runnable in action.State value = 11",並無輸出"State runnable in action.State value = 22",這是由於沒法預測強引用對象被回收的時機。java.lang.ref.Cleaner主要是用於預防實現了AutoCloseable接口的實例忘記調用close()方法在對象被垃圾收集器回收的時候(內存回收)作一個兜底的清理工做,在JDK9以後,java.lang.ref.Cleaner主要是爲了替代已經標識爲過時的Object#finalize()方法。

擴展閱讀:能夠注意閱讀一下《Effective Java 3rd》的第8小節,摘抄部份內容以下:終結方法(Finalizer)是不可預知的,不少時候是危險的,並且通常狀況下是沒必要要的。...在Java 9中,終結方法已經被遺棄了,但它們仍被Java類庫使用,相應用來替代終結方法的是清理方法(cleaner)。比起終結方法,清理方法相對安全點,但還是不能夠預知的,運行慢的,並且通常狀況下是沒必要要的。

JDK9中有不少原來使用覆蓋Object#finalize()方法的清理工做實現都替換爲java.lang.ref.Cleaner,可是仍然不鼓勵使用這種方式。

Reference和ReferenceQueue配合使用

前面基本介紹完了全部類型引用以及相關的源碼,可是還沒有提供例子說明ReferenceReferenceQueue是怎麼配合使用的。舉個例子:

public class ReferenceQueueMain {

    public static void main(String[] args) throws Exception {
        ReferenceQueue<WeakReferenceObject> queue = new ReferenceQueue<>();
        WeakReferenceObject object = new WeakReferenceObject();
        WeakReference<WeakReferenceObject> weakReference = new WeakReference<>(object, queue);
        System.out.println(weakReference);
        object = null;
        System.gc();
        Thread.sleep(500);
        while (true) {
            Reference<? extends WeakReferenceObject> reference = queue.poll();
            if (null == reference) {
                Thread.sleep(100);
            } else {
                System.out.println(reference);
                System.out.println(reference.get());
                break;
            }
        }
    }

    private static class WeakReferenceObject {

        @Override
        public String toString() {
            return "WeakReferenceObject";
        }
    }
}

運行後輸出結果是:

java.lang.ref.WeakReference@6537cf78
java.lang.ref.WeakReference@6537cf78
null

可見輪詢ReferenceQueue實例獲得的弱引用實例和建立的是一致的,只是它持有的關聯的對象已經被回收,獲得null。上面的ReferenceQueue#poll()方法也能夠替換爲ReferenceQueue#remove(),這樣子就不用寫在死循環中,由於ReferenceQueue#remove()會阻塞到有元素能夠出隊。經過輪詢綁定到Reference實例的ReferenceQueue實例,就能夠得知Reference實例當前的狀態而且判斷它關聯的咱們真正關注的對象是否被回收。

小結

  • Reference非強引用的其餘三種引用的共同父類。
  • ReferenceQueue只存儲了引用鏈表的頭節點,提供了引用鏈表的操做,實際上,引用鏈表是Reference實例內部變量存儲的。
  • ReferenceHandler守護線程線程由Reference的靜態代碼塊建立和運行,做用是處理pending鏈表的引用元素使之狀態變動,伴隨着ReferenceQueue的相關操做。
  • Finalizer守護線程是由Finalizer類的靜態代碼塊建立和運行的,做用是處理Finalizer類內部維護的F-Queue鏈表(鏈表元素入隊操做由JVM實現)的元素調用關聯對象的finalize()方法。
  • ReferenceHandler守護線程線和Finalizer守護線程共同協做才能使引用類型對象內存回收系統的工做可以正常進行。

四種引用類型的總結

引用類型 被垃圾收集器回收的時機 主要用途 生存週期
強引用 直到內存溢出也不會回收 廣泛對象的狀態 從建立到JVM實例終止運行
軟引用 垃圾回收而且內存不足時 有用但非必須的對象緩存 從建立到垃圾回收而且內存不足時
弱引用 垃圾回收時 非必須的對象緩存 上一次垃圾回收結束到下一次垃圾回收開始
虛引用 - 關聯的對象被垃圾收集器回收時候獲得一個系統通知 -

參考資料:

  • JDK11部分源碼。
  • 《深刻理解Java虛擬機-2nd》- 這本書算是國內書籍寫得比較良心的一本了,不過有不少小的問題或者筆誤之處,須要自行發現和修正。

我的博客

(過年比較懶,好久沒發文 e-a-20190215 c-14-d)

相關文章
相關標籤/搜索