編輯:關於Android編程
寫在前面: Reference本身是一個接口,表示一個引用,不能直接使用,有四個它的派生類供我們使用,它們分別是:SoftReference,WeakReference,PhantomReference,FinalizerReference .其中SoftReference,WeakReference和 PhantomReference的區別與使用Google一下已經有大把的介紹資料,因此本文對此只簡單說明帶過,主要給大家介紹你不知道的Reference.
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所引用的對象在發生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.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 之前,先關注下前面介紹的三個引用類的共同的父類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主要是為了協助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的學習過程。希望能對朋友們有所幫助。感謝您能讀到這裡,有各種意見歡迎指出討論。
這裡先貼出原文,下次再來翻譯;:p 原文地址:http://developer.android.com/training/basics/actionbar/styli
Android越來越普及,那已經安裝的應用要如何更新呢?在應用市場中常會有顯示某某應用已經更新之類的信息,那我們是否也可以實現類似的功能呢?如果要實現又要做
想起前段時間的物聯網的外包開發,經常遇到通過wifi接受的數據,要通過轉換成十六進制字符串,或者最後又是十進制數據。都是根據雙方的協議來開發的。那麼我發送過去的數據也需要
顯示百度地圖百度地圖SDK為開發者提供了便捷的顯示百度地圖數據的接口,通過以下幾步操作,即可在您的應用中使用百度地圖數據:第一步:創建並配置工程(具體方法參見工程配置部分