Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 大貓品Android[三][Reference深入淺出]

大貓品Android[三][Reference深入淺出]

編輯:關於Android編程

寫在前面: Reference本身是一個接口,表示一個引用,不能直接使用,有四個它的派生類供我們使用,它們分別是:SoftReference,WeakReference,PhantomReference,FinalizerReference .其中SoftReference,WeakReference和 PhantomReference的區別與使用Google一下已經有大把的介紹資料,因此本文對此只簡單說明帶過,主要給大家介紹你不知道的Reference.

一. SoftReference

SoftReference表示一個對象的軟引用, SoftReference所引用的對象在發生GC時,如果該對象只被這個SoftReference所引用,那麼在內存使用情況已經比較緊張的情況下會釋放其所占用的內存,若內存比較充實,則不會釋放其所占用的內存.比較常用於一些Cache的實現.

其構造函數中允許傳入一個ReferenceQueue.其代碼如下所示:

SoftReference.java

public class SoftReference extends Reference {

    /**
     * Constructs a new soft reference to the given referent. The newly created
     * reference is not registered with any reference queue.
     *
     * @param r the referent to track
     */
    public SoftReference(T r) {
        super(r, null);
    }

    /**
     * Constructs a new soft reference to the given referent. The newly created
     * reference is registered with the given reference queue.
     *
     * @param r the referent to track
     * @param q the queue to register to the reference object with. A null value
     *          results in a weak reference that is not associated with any
     *          queue.
     */
    public SoftReference(T r, ReferenceQueue q) {
        super(r, q);
    }
}

這個ReferenceQueue才是本文重點之一,後面會專門說到.

二.WeakReference

WeakReference表示一個對象的弱引用,WeakReference所引用的對象在發生GC時,如果該對象只被這個WeakReference所引用,那麼不管當前內存使用情況如何都會釋放其所占用的內存.

其構造函數中允許傳入一個ReferenceQueue.這個ReferenceQueue才是本文重點之一,後面會專門說到.WeakReference與SoftReference一樣派生於Reference類:

WeakReference.java

public class WeakReference extends Reference {

    /**
     * Constructs a new weak reference to the given referent. The newly created
     * reference is not registered with any reference queue.
     *
     * @param r the referent to track
     */
    public WeakReference(T r) {
        super(r, null);
    }

    /**
     * Constructs a new weak reference to the given referent. The newly created
     * reference is registered with the given reference queue.
     *
     * @param r the referent to track
     * @param q the queue to register to the reference object with. A null value
     *          results in a weak reference that is not associated with any
     *          queue.
     */
    public WeakReference(T r, ReferenceQueue q) {
        super(r, q);
    }
}

三. PhantomReference

PhantomReference表示一個虛引用, 說白了其無法引用一個對象,即對對象的生命周期沒有影響.

其代碼如下:

PhantomReference.java

public class PhantomReference extends Reference {

    /**
     * Constructs a new phantom reference and registers it with the given
     * reference queue. The reference queue may be {@code null}, but this case
     * does not make any sense, since the reference will never be enqueued, and
     * the {@link #get()} method always returns {@code null}.
     *
     * @param r the referent to track
     * @param q the queue to register the phantom reference object with
     */
    public PhantomReference(T r, ReferenceQueue q) {
        super(r, q);
    }

    /**
     * Returns {@code null}.  The referent of a phantom reference is not
     * accessible.
     *
     * @return {@code null} (always)
     */
    @Override
    public T get() {
        return null;
    }
}

可以看到他重寫了Reference的get方法直接返回null.所以說它並不是為了改變某個對象的生命周期而存在的.它常用於跟蹤某些對象的生命周期狀態,它只有一個接受ReferenceQueue的構造方法.正是這個ReferenceQueue的神奇功效幫助PhantomReference實現了跟蹤對象生命周期的功能.這裡忍不住再一次鋪墊,ReferenceQueue馬上就來.

四.ReferenceQueue

在介紹 ReferenceQueue 之前,先關注下前面介紹的三個引用類的共同的父類Reference.
Reference.java

public abstract class Reference {

     ...

    /**
     * The object to which this reference refers.
     * VM requirement: this field must be called "referent"
     * and be an object.
     */
    volatile T referent;

    /**
     * If non-null, the queue on which this reference will be enqueued
     * when the referent is appropriately reachable.
     * VM requirement: this field must be called "queue"
     * and be a java.lang.ref.ReferenceQueue.
     */
    volatile ReferenceQueue queue;

    /**
     * Used internally by java.lang.ref.ReferenceQueue.
     * VM requirement: this field must be called "queueNext"
     * and be a java.lang.ref.Reference.
     */
    @SuppressWarnings("unchecked")
    volatile Reference queueNext;


    /**
     * Constructs a new instance of this class.
     */
    Reference() {
    }

    Reference(T r, ReferenceQueue q) {
        referent = r;
        queue = q;
    }

    /**
     * Adds an object to its reference queue.
     *
     * @return {@code true} if this call has caused the {@code Reference} to
     * become enqueued, or {@code false} otherwise
     *
     * @hide
     */
    public final synchronized boolean enqueueInternal() {
        if (queue != null && queueNext == null) {
            queue.enqueue(this);
            queue = null;
            return true;
        }
        return false;
    }

    /**
     * Forces the reference object to be enqueued if it has been associated with
     * a queue.
     *
     * @return {@code true} if this call has caused the {@code Reference} to
     * become enqueued, or {@code false} otherwise
     */
    public boolean enqueue() {
        return enqueueInternal();
    }

    ...

}

這裡主要關注Reference的構造方法和equeue方法。看到在Reference中有兩個構造方法,其中傳入的ReferenceQueue的構造方法將傳入的ReferenceQueue保存在其queue這個成員變量中。並且通過enqueue方法調用enqueueInternal將自己添加到queue中。這裡大家注意Reference中可能會有一個用於保存自己的queue隊列,後面會發現其巧妙的使用方式。ReferenceQueue顧名思義,就是一個引用隊列,其內部通過兩個Reference類型的成員變量head和tail來構成一個鏈表結構.並提供了入隊出隊的相應方法,相關代碼如下:

ReferenceQueue.java

public class ReferenceQueue {
    private static final int NANOS_PER_MILLI = 1000000;

    private Reference head;
    private Reference tail;

    /**
     * Constructs a new instance of this class.
     */
    public ReferenceQueue() {
    }

    /**
     * Returns the next available reference from the queue, removing it in the
     * process. Does not wait for a reference to become available.
     *
     * @return the next available reference, or {@code null} if no reference is
     *         immediately available
     */
    @SuppressWarnings("unchecked")
    public synchronized Reference poll() {
        if (head == null) {
            return null;
        }

        Reference ret = head;

        if (head == tail) {
            tail = null;
            head = null;
        } else {
            head = head.queueNext;
        }

        ret.queueNext = null;
        return ret;
    }

    /**
     * Returns the next available reference from the queue, removing it in the
     * process. Waits indefinitely for a reference to become available.
     *
     * @throws InterruptedException if the blocking call was interrupted
     */
    public Reference remove() throws InterruptedException {
        return remove(0L);
    }

    /**
     * Returns the next available reference from the queue, removing it in the
     * process. Waits for a reference to become available or the given timeout
     * period to elapse, whichever happens first.
     *
     * @param timeoutMillis maximum time to spend waiting for a reference object
     *     to become available. A value of {@code 0} results in the method
     *     waiting indefinitely.
     * @return the next available reference, or {@code null} if no reference
     *     becomes available within the timeout period
     * @throws IllegalArgumentException if {@code timeoutMillis < 0}.
     * @throws InterruptedException if the blocking call was interrupted
     */
    public synchronized Reference remove(long timeoutMillis)
            throws InterruptedException {
        if (timeoutMillis < 0) {
            throw new IllegalArgumentException("timeout < 0: " + timeoutMillis);
        }

        if (head != null) {
            return poll();
        }

        // avoid overflow: if total > 292 years, just wait forever
        if (timeoutMillis == 0 || (timeoutMillis > Long.MAX_VALUE / NANOS_PER_MILLI)) {
            do {
                wait(0);
            } while (head == null);
            return poll();
        }

        // guaranteed to not overflow
        long nanosToWait = timeoutMillis * NANOS_PER_MILLI;
        int timeoutNanos = 0;

        // wait until notified or the timeout has elapsed
        long startTime = System.nanoTime();
        while (true) {
            wait(timeoutMillis, timeoutNanos);
            if (head != null) {
                break;
            }
            long nanosElapsed = System.nanoTime() - startTime;
            long nanosRemaining = nanosToWait - nanosElapsed;
            if (nanosRemaining <= 0) {
                break;
            }
            timeoutMillis = nanosRemaining / NANOS_PER_MILLI;
            timeoutNanos = (int) (nanosRemaining - timeoutMillis * NANOS_PER_MILLI);
        }
        return poll();
    }

    /**
     * Enqueue the reference object on the receiver.
     *
     * @param reference
     *            reference object to be enqueued.
     */
    synchronized void enqueue(Reference reference) {
        if (tail == null) {
            head = reference;
        } else {
            tail.queueNext = reference;
        }

        // The newly enqueued reference becomes the new tail, and always
        // points to itself.
        tail = reference;
        tail.queueNext = reference;
        notify();
    }

    /** @hide */
    public static Reference unenqueued = null;

    static void add(Reference list) {
        synchronized (ReferenceQueue.class) {
            if (unenqueued == null) {
                unenqueued = list;
            } else {
                // Find the last element in unenqueued.
                Reference last = unenqueued;
                while (last.pendingNext != unenqueued) {
                  last = last.pendingNext;
                }
                // Add our list to the end. Update the pendingNext to point back to enqueued.
                last.pendingNext = list;
                last = list;
                while (last.pendingNext != list) {
                    last = last.pendingNext;
                }
                last.pendingNext = unenqueued;
            }
            ReferenceQueue.class.notifyAll();
        }
    }
}

通過poll方法彈出隊列頭部存儲的Reference.通過remove方法可以將poll變成block的,即隊列為空時remove方法可以試當前線程阻塞住,等到enqueue時通過notify再將block喚醒.大家需要著重注意的是最後@hide起來的那個static成員變量unenqueued和add方法。通過add方法將參數表示的Reference添加到unenqueued描述的一個隊列中。並通過ReferenceQueue.class.notifyAll()喚醒某處被阻塞住的線程。這裡留兩個疑問:1.喚醒的是哪個線程?2.這個add方法又是在哪裡被調用的呢?我們先來看一下Daemons.java中的一個守護線程ReferenceQueueDaemon干了什麼,真相自會浮出水面。
Daemons.java

public final class Daemons {
/**
     * This heap management thread moves elements from the garbage collector's
     * pending list to the managed reference queue.
     */
    private static class ReferenceQueueDaemon extends Daemon {
        private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon();

        ReferenceQueueDaemon() {
            super("ReferenceQueueDaemon");
        }

        @Override public void run() {
            while (isRunning()) {
                Reference list;
                try {
                    synchronized (ReferenceQueue.class) {
                        while (ReferenceQueue.unenqueued == null) {
                            ReferenceQueue.class.wait();
                        }
                        list = ReferenceQueue.unenqueued;
                        ReferenceQueue.unenqueued = null;
                    }
                } catch (InterruptedException e) {
                    continue;
                }
                enqueue(list);
            }
        }

        private void enqueue(Reference list) {
            Reference start = list;
            do {
                // pendingNext is owned by the GC so no synchronization is required.
                Reference next = list.pendingNext;
                list.pendingNext = null;
                list.enqueueInternal();
                list = next;
            } while (list != start);
        }
    }

Daemons.java中定義了4個守護線程(Android_M之前是5個,其中就包括鼎鼎大名的GC線程。但再Android_M中GCDaemon和HeapTrimDaemon合並了)。並且在fork出進程的時候會將Daemons.java中定義的幾個守護線程都跑起來。關於Daemons在後續GC的專題討論中我會具體介紹。這裡我們主要看其中的一個守護線程ReferenceQueueDaemon,我們看它的run方法中首先判斷ReferenceQueue的靜態成員變量unqueue是否為空,空則阻塞住當前線程,這裡的ReferenceQueue.class.wait()有點似曾相識,沒錯,剛剛我們再找ReferenceQueue的add方法喚醒了哪個線程,喚醒就是這個ReferenceQueueDaemon守護線程。如果不為空,則通過enqueue調用unqueue所指向的Reference的enqueueInternal()方法。前面分析Reference的enqueueInternal()方法知道它將自己所表示的Reference添加到自己的queue成員中,這個queue成員就是構造Referene時傳進去的ReferenceQueue。現在上面提到的問題1解決了,那問題2.ReferenceQueue的add方法是哪裡調用的呢?答案是從虛擬機裡面調出來的,在虛擬機內部完成GC時就會通過JNI反調回ReferenceQueue的add方法中。關於虛擬機內部反調回ReferenceQueue的過程再後續的GC專題會詳細敘述。

到這裡也許會有一點暈,來個小總結:

ReferenceQueueDaemon在應用啟動後就開始工作,任務是從ReferenceQueue.unqueue中讀出需要處理的Reference。並將讀出的Reference放入構造其自身時傳入的ReferenceQueue中。

虛擬機在每次GC完成後會調用ReferenceQueue.add方法將這次GC釋放的內存的對象所對應的Reference添加到ReferenceQueue.unqueue中

一個典型的生產者消費者模型。

當然,當不使用Reference時,或者構造Reference不傳入ReferenceQueue時,這部分處理工作其實是直接跳過的。

所以說到這裡,ReferenceQueue的作用也很明顯了,它就是起到了一個監控對象生命周期的作用。即當對象被GC回收時,倘若為它創建了帶ReferenceQueue的Reference,那麼會將這個Reference加入到構造它時傳入的ReferenceQueue中。這樣我們遍歷這個ReferenceQueue就知道被監控的對象是否被GC回收了。前面說的PhantomReference通常用來監控對象的生命周期也就是這個原理。

五. FinalizerReference.

FinalizerReference主要是為了協助FinalizerDaemon守護線程完成對象的finalize工作而生的.
其主要代碼如下:
FinalizerReference.java


/**
 * @hide
 */
public final class FinalizerReference extends Reference {
    // This queue contains those objects eligible for finalization.
    public static final ReferenceQueue

可以看到 FinalizerReference內部定義了一個static的ReferenceQueue對象queue.這個queue在add方法中作為FinalizerReference的構造方法參數構造了一個FinalizerReference對象,並將構造的FinalizerReference對象加入到他自身維護的一個隊列中.remove方法從其自身維護的隊列中刪除指定的Reference。另外看到FinalizerReference的get方法返回的是zombie成員。這個成員是在虛擬機中從referent拷貝過來的(後面介紹GC時會詳細說明)。

簡單來說,FinalizerReference就是一個派生自Reference的類,內部實現了一個由head,prev,next維護的隊列,還有一個自己定義的成員變量queue。它的蹊跷之處就在這個queue成員變量和add方法。

在其add方法中使用這個queue和參數中的一個對象構造了一個FinalizerReference,並將其插入自己維護的隊列中。根據前面對ReferenceQueue的說明,當這個被FinalizerReference引用的對象被GC釋放其所占用的內存堆空間時,會把這個對象的FinalizerReference引用插入到這個queue中。這個add方法同樣是從虛擬機中反調回來的,當一個對象實現了finalize方法,虛擬機中能夠檢測到,並且反調這個add方法將實現了finalize方法的對象當做參數傳出來。即所有實現了finalize方法的對象的生命周期都被FinalizerReference的queue所監控著,當GC發生時queue中就會插入當前正准備釋放內存的對象的FinalizerReference引用。到這裡能很清晰看出這個也是一個典型的圍繞這個queue成員變量的生產者消費者模型,生產者已經找到,接下來看下哪裡去消費這個queue呢?我們還是將目光轉向Daemons.java

Daemons.java

public final class Daemons {

    ...
        private static class FinalizerDaemon extends Daemon {
        private static final FinalizerDaemon INSTANCE = new FinalizerDaemon();
        private final ReferenceQueue

FinalizerDaemon是Daemons.java中定義的另一個守護線程,FinalizerReference中定義的queue的消費者就是它。它內部定義了一個ReferenceQueue類型的對象queue,並將其賦值為前面說的FinalizerReference中的定義的那個queue。run方法中通過ReferenceQueue的remove方法把保存在queue中的Reference獲取出來並通過doFinalize方法做下一步處理。前面提過ReferenceQueue的remove方法是阻塞的,在隊列中沒有Reference時將阻塞直到有Reference入隊。我們看一下doFinalize方法,通過從隊列中獲取出來的reference的get方法獲取到被引用的真實對象,並在這裡調用該對象的finalize方法。但在這之前會通過FinalizerWatchdogDaemon.INSTANCE.notify()喚醒FinalizerWatchdogDaemon守護線程,FinalizerWatchdogDaemon在稍後介紹。

總結起來,FinalizerQueue和FinalizerDaemon組合起來完成了在合適的時機去調用我們實現的finalize方法的工作:虛擬機檢測到有對象實現了finalize方法會調用FinalizerQueue的add方法使得在GC的時候能將實現了finalize方法的對象的引用加入到FinalizerQueue的queue成員中。而FinalizerDaemon則從FinalizerQueue的queue中取出跟蹤的引用並調用被引用對象的finalize方法。

上面提到的FinalizerWatchdogDaemon同樣是定義在Daemons.java中的一個守護線程。它的代碼比較簡單,感興趣的朋友可以去看一下。這裡主要介紹下它的作用。它主要用來監控finalize方法執行的時長,並在finalize執行超時時會拋出finalize() timed out異常並退出進程。所以我們在實現finalize方法的時候一定不能在finalize方法內做太過負責的事情。另外從這裡也看出,如果對象實現了finalize方法,那麼它的內存會等到其finalize方法執行完成才真正釋放,這從某種程度上說也推遲啦GC回收內存的進度。所以不是萬不得已個人是不建議實現finalize方法的。

以上就是我對Android中的Reference的學習過程。希望能對朋友們有所幫助。感謝您能讀到這裡,有各種意見歡迎指出討論。

  1. 上一頁:
  2. 下一頁:
Copyright © Android教程網 All Rights Reserved