Java/Android中的引用類型及WeakReference應用實踐

NO IMAGE

一、背景

一般意義上而言,Java/Android中的引用類型包括強引用、軟引用、弱引用、虛引用。不同的引用類型具有各自適用的應用場景,並與JVM的GC直接相關。

作為Java/Android中的引用類型之一,WeakReference被大量的使用到系統源碼、基礎工具甚至具體的業務邏輯中。在解決需要異步使用目標對象實體、且又不影響目標對象實體的生命週期的場景中,具有天然優勢。同時,還能進一步判斷目標對象實體當前所處的GC階段,如當前是否GC roots可達,亦或者已經被GC回收。

二、四種引用類型

2.1 強引用與GC可達

默認情況下,我們直接指向對象的引用類型為強引用,也是我們天天寫代碼必定會用到的。

在Java/Android應用層面上,強引用更多的只是單純的概念層次上的,引用變量定義時對應的類型即為實際指向對象的類型或其父類型。如:

Person person = new Person();

其中,person就是一個強引用變量,指向的是Person類型的對象實體。

從GC的視角來看,new Person()對應的對象實體,是儲存在堆中的(逃逸先不必考慮)。person這個引用變量,依據實際的變量定義的位置,有可能分配在棧中存儲(如在方法中定義),也有可能分配在堆中存儲(如作為類的字段)。

引用關係畫一個簡單的圖,大概如下所示:

Java/Android中的引用類型及WeakReference應用實踐

現實中,對同一個對象實體,往往會具有複雜的多個引用指向,如最常見的將對象的引用變量作為實參傳遞,形參接收後會指向同一對象實體等等。因此,現實中的對象引用與實體關係比較複雜,可能如下:

Java/Android中的引用類型及WeakReference應用實踐

GC時,通過可達性去分析,如果沒有強引用指向對象實體,或者即使有強引用指向,但強引用的所處的對象自身,已經不能從GC Roots可達了,這時GC,此對象實體會被垃圾回收。

從對象實體的生命週期視角來看,new Person()時開始給對象分配內存空間,並調用構造器等進行初始化,此時,對象生成。一旦在GC Roots中沒有強引用直達,對象實體變成“孤魂野鬼”,對象生命週期走向完結,對應內存空間可以被回收。

只要對象實體存在強應用可達,就不會被垃圾回收,直至發生OOM,進程終止。

2.2 Reference 與 ReferenceQueue

Java源碼中的java.lang.ref包,對應的是應用類型和引用隊列的類定義。在Android中,對應部分具體源碼上有稍許更改,但整體上類職責與實現邏輯是類似的,不妨礙整體上的對引用類型的分析。

為了陳述方便,同時不引起歧義,先界定幾個基本概念,以及對應的具體解釋。

1,目標對象實體。表示通常意義上創建出來的對象,例如上述強引用示例中的new Person()即表示一個Person類型的對象實體。此對象可以被引用對象中的referent屬性去指向。

2,引用對象。由具體的引用類型類(如WeakReference、SoftReference、PhantomReference)所創建出來的對象。引用對象在創建時,外部會將目標對象實體傳入進來,從而使得引用對象中的referent屬性去指向目標對象實體

3,referent屬性引用對象中的referent屬性指向的是實際的目標對象實體

4,引用隊列引用對象創建時,由外部傳入ReferenceQueue類型的對象,引用隊列中存儲的是引用對象,並且,是會在特定情況下由虛擬機將引用對象入隊。存在於引用隊列中的引用對象,表明此引用對象referent屬性所指向的目標對象實體已經被垃圾回收。

Reference類本身,是一個抽象類,作為具體引用類型的基類,定義了基本的類屬性與行為。主體類結構如下所示:

Java/Android中的引用類型及WeakReference應用實踐

從類的註釋上可以看出,Reference類對所有子類提供了一致的操作行為,並在運行時是會與虛擬機中的垃圾收集器緊密協作的,實際使用中,我們只能使用現有的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.
*
* @author   Mark Reinhold
* @since    1.2
*/
public abstract class Reference<T> {
....
}

Reference類比較關鍵的部分摘錄如下:

public abstract class Reference<T> {
....
private T referent;         /* Treated specially by GC */
volatile ReferenceQueue<? super T> queue;
/**
* Returns this reference object's referent.  If this reference object has
* been cleared, either by the program or by the garbage collector, then
* this method returns <code>null</code>.
*
* @return   The object to which this reference refers, or
*           <code>null</code> if this reference object has been cleared
*/
public T get() {
return this.referent;
}
/**
* Clears this reference object.  Invoking this method will not cause this
* object to be enqueued.
*
* <p> This method is invoked only by Java code; when the garbage collector
* clears references it does so directly, without invoking this method.
*/
public void clear() {
this.referent = 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類有兩個構造器,其中T referent是一個泛型形式表示的形參,指向的是目標對象實體ReferenceQueue<? super T> queue表示的是一個引用隊列,隊列內存儲的元素是引用對象,外部調用方通過get()方法獲取目標對象實體。如果引用對象中的referent屬性為nullget()方法將返回null

referent屬性為null存在如下兩個觸發場景:
1,虛擬機進行垃圾回收時;
2,人為的調用引用對象clear()方法。

其中區別在於,人為的調用clear()方法,並不會使得此引用對象進入引用隊列

ReferenceQueue,表示引用隊列,類的職責可以從類註釋中看出來。

/**
* Reference queues, to which registered reference objects are appended by the
*  * garbage collector after the appropriate reachability changes are detected.
*  *
* @author   Mark Reinhold
* @since    1.2
*/
public class ReferenceQueue<T> {
....
}

引用隊列中存儲的元素,是引用對象,垃圾回收器會在引用對象中的目標對象實體不再可達時,對目標對象實體進行垃圾回收,並將對應的引用對象放入引用隊列中。因此,我們可以通過引用對象中是否存在引用對象,去判斷對應的目標對象實體是否已經被垃圾回收。

Reference是一個抽象類,實際使用時,外部用的是其具體的子類,依據實際的需求場景,對應選擇使用WeakReferenceSoftReferencePhantomReference

2.3 軟引用

首先要說明一下,一般意義上的軟引用弱引用虛引用,實際上指的都是引用對象中的指向目標對象實體referent屬性。而非指此引用對象本身。因為此referent屬性才是真正指向的目標對象實體,且存在於具體的引用對象中,具有具體的引用類型的特性。當然,這個特性更多是虛擬機賦予的。

例如:眾所周知的,當目標對象實體沒有強引用可達,但有軟引用指向時,在內存不夠用時,才會回收目標對象實體

因此,我們發現,只要內存夠用(是否夠用由虛擬機判斷),即使目標對象實體只是軟引用可達的,目標對象實體也不會被GC,會一直存活。

可以通過實際的例子看一下軟引用的效果。

public class SoftReferenceTest {
public static void main(String[] args) {
A a = new A();
ReferenceQueue<A> rq = new ReferenceQueue<A>();
SoftReference<A> srA = new SoftReference<A>(a, rq);
a = null;
if (srA.get() == null) {
System.out.println("a對象進入垃圾回收流程");
} else {
System.out.println("a對象尚未進入垃圾回收流程" + srA.get());
}
// 通知系統進行垃圾回收
System.gc();
try {
Thread.currentThread().sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
if (srA.get() == null) {
System.out.println("a對象進入垃圾回收流程");
} else {
System.out.println("a對象尚未進入垃圾回收流程" + srA.get());
}
System.out.println("引用對象:" + rq.poll());
}
}
class A {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("in A finalize");
}
}

運行結果為:

a對象尚未進入垃圾回收流程[email protected]
a對象尚未進入垃圾回收流程[email protected]
引用對象:null

當a對象沒有強引用可達時,只有軟引用可達,此時,無論系統是否發生GC,a對象的生命週期依然是存活的,不會被垃圾回收。也正因為如下,引用隊列中是不存在對應的srA這個引用對象的。

上述過程對A這個類型的目標對象實體的引用關係,起始是這樣的:

Java/Android中的引用類型及WeakReference應用實踐

當執行a = null時,此時引用關係如下:

Java/Android中的引用類型及WeakReference應用實踐

強引用斷裂,但不影響引用對象中的對A對象這個目標對象實體的引用關係。

因此,只要內存足夠,通過引用對象get()方法,都可以獲取到A對象實體。

如果恰巧此時,內存不夠了呢,虛擬機在GC流程中,會將引用對象referent強制置為null,此時A對象實體徹底變成“孤魂野鬼”,可以被垃圾回收。

當然,這裡需要說明一點的是,示例中只是一個demo。當方法執行完畢後,方法中所佔用的棧內存空間的引用(A a、SoftReference srA)會自動出棧,A對象實體也會自動變成“孤魂野鬼”,直至等待被垃圾回收。

實際使用中,SoftReference不一定被經常用到,雖然SoftReference可以適當應用到如緩存等場景,但一般更通用的建議是使用如LruCache等緩存方案。

2.4 弱引用

與弱引用直接關聯的引用對象類型為WeakReference。弱引用的特性如下:

目標對象實體沒有強引用可達,但有弱引用可達,此時,在發生GC之前,此目標對象實體都是存活的,一旦發生GC,GC過程中會將弱引用對象中的referent屬性置為null,並直接將此目標對象實體進行回收,並將此引用對象入隊到引用隊列中。

繼續看一個具體的示例:

public class WeakReferenceTest {
public static void main(String[] args) {
A a = new A();
ReferenceQueue<A> rq = new ReferenceQueue<A>();
WeakReference<A> wrA = new WeakReference<A>(a, rq);
System.out.println("引用對象:" + wrA);
a = null;
if (wrA.get() == null) {
System.out.println("a對象進入垃圾回收流程");
} else {
System.out.println("a對象尚未進入垃圾回收流程" + wrA.get());
}
// 通知系統進行垃圾回收
System.gc();
try {
Thread.currentThread().sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
if (wrA.get() == null) {
System.out.println("a對象進入垃圾回收流程");
} else {
System.out.println("a對象尚未進入垃圾回收流程" + wrA.get());
}
System.out.println("引用對象:" + rq.poll());
}
static class A {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("in A finalize");
}
}
}

輸出結果為:

引用對象:[email protected]
a對象尚未進入垃圾回收流程[email protected]
in A finalize
a對象進入垃圾回收流程
引用對象:[email protected]

示例代碼中,System.gc();執行後,之所以讓當前線程sleep(1),是基於進一步確保GC線程能被調度執行考慮的。最終的輸運行結果,對應的弱引用對象,被入隊到引用隊列中,表明A對象實體已經被垃圾回收。

引用關係起初是這樣的:

Java/Android中的引用類型及WeakReference應用實踐

執行a = null時,此時引用關係如下:

Java/Android中的引用類型及WeakReference應用實踐

當虛擬機GC時,首先會將referent置為null,引用關係變為如下:

Java/Android中的引用類型及WeakReference應用實踐

此時,A對象實體已經變成“孤魂野鬼”,可以被垃圾回收。GC過程中,弱引用對象入隊引用隊列

Java/Android中的引用類型及WeakReference應用實踐

由此,我們發現,弱引用一個強大的地方在於,弱引用本質上,是不改變目標對象實體的生命週期的,也不影響目標對象實體被GC的時機,並且,還提供了一種機制,即基於引用隊列下的,可以直接去監測目標對象實體是否已經被GC。

這無疑是相當強大的,相當於提供了一種可以監測到對象是否被GC的方法,且不影響到對象生命週期本身。

2.5 虛引用

無論是SoftReferenceWeakReference還是PhantomReference,作為Reference類的子類,自身更多隻是作為引用類型的對象,去標記用的,類中沒有過多的自身的邏輯。與引用類型的邏輯處理過程,絕大部分都是在虛擬機中實現的。

當然,有一大不同的是,PhantomReference類中,重寫了T get()方法,直接返回了null

public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent.  Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return  <code>null</code>
*/
public T get() {
return null;
}
/**
* Creates a new phantom reference that refers to the given object and
* is registered with the given queue.
*
* <p> It is possible to create a phantom reference with a <tt>null</tt>
* queue, but such a reference is completely useless: Its <tt>get</tt>
* method will always return null and, since it does not have a queue, it
* will never be enqueued.
*
* @param referent the object the new phantom reference will refer to
* @param q the queue with which the reference is to be registered,
*          or <tt>null</tt> if registration is not required
*/
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}

也就是說,通過虛引用對象get()方法,是無法獲取到目標對象實體的。但實際上,虛引用對象中的referent還是指向目標對象實體的。也正因為如此,使用到虛引用對象時,往往都需要傳一個引用隊列,否則,構建的虛引用就沒有任何意義了。

虛擬機在GC時,接下來的處理流程與弱引用類似。目標對象實體被GC後,會被入隊到引用隊列中。

三、WeakReference應用實踐

3.1 WeakReference特性總結

相比SoftReferencePhantomReferenceWeakReference應用更加普遍。主要得益於WeakReference提供的特性:
1,提供了一種監測目標對象實體是否已經被垃圾回收的方法;
2,同時不改變目標對象實體本身的生命週期;
3,對外提供了T get()方法去嘗試獲取到目標對象。

下面具體看一下WeakReference在Java/Android中的使用場景。

3.2 通過WeakReference處理Handler內存洩漏

不少人第一次接觸到WeakReference這個概念,是在Activity中的Handler可能引起的內存洩露中。

Activity中的Handler內存洩露,都比較熟。Activity中的Handler,如果以非靜態內部類的方式存在,默認會持有外部類,即Activity的引用,在Activity對象中通過Handler發出去的消息,是會被加入到消息隊列中的,待Looper不斷輪循,在MQ中取到此消息時,才會進行消息的處理,如handleMessage。也就是說,Handler默認持有Activity的引用,同時消息處理過程整體上是異步的。此時,在消息被處理前,如果按下了如back鍵等,Activity是會出棧的,一旦GC發生,理論上此Activity對象也應該被GC,但由於被Handler持有,導致強引用可達,內存無法回收,且handleMessage依然可以執行。

因此,往往都建議將Handler定義成靜態的內存類,或者外部類形式,此時,不再默認持有Activity引用,但如果handleMessage中又需要使用到Activity中的屬性時,這種情況下,通過WeakReference實現,就是一個極佳的使用場景。

重新梳理下上述的流程:本質上就是Activity對象中需要做一件事情,這個事情是一個未來發生的,異步的事情。最佳的期望應該是,當Acitivity對象生命週期走向完結,這件事情與Acitivity直接相關的部分應當自然終止。因為期望上,此時Activity對象已經被銷燬,甚至被垃圾回收。那與Acitivity直接相關的這部分自然也就沒有意義了。

我們發現,這其實完全符合WeakReference的特性,通過WeakReference對象中的T referent屬性,弱引用到Activity對象實體,當T get()null時,直接將與Activity對象有關的事情終止即可。這也是經典的Handler內存洩露的處理方式。

3.3 WeakHashMap

WeakHashMapHashMap基本實現過程是一樣的,根本的區別在於,其內部的Entry繼承的是WeakReferenceEntry中的key具有弱引用特性。具體定義如下:

/**
* The entries in this hash table extend WeakReference, using its main ref
* field as the key.
*/
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
/**
* Creates new entry.
*/
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash  = hash;
this.next  = next;
}
@SuppressWarnings("unchecked")
public K getKey() {
return (K) WeakHashMap.unmaskNull(get());
}
public V getValue() {
return value;
}
public V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
K k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
V v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public int hashCode() {
K k = getKey();
V v = getValue();
return Objects.hashCode(k) ^ Objects.hashCode(v);
}
public String toString() {
return getKey() + "=" + getValue();
}
}

因此,當Entrykey指向的目標對象實體本身沒有其他強引用或軟引用可達時,GC發生時,此目標對象實體會被收回,Entrykey會被置為null,並且,此Entry對象,將被入隊到引用隊列中。但直到此時,對於WeakHashMap而言,這些keynullEntry還是作為一個個item項存在的,依然處於之前的位置。

實際上,這些Entry已經沒有必要存在了,因為key已經從起初的指向目標對象實體變成了null,作為key-value這種映射關係,已經發生了破壞,且key原本指向的目標對象實體生命週期也已經走向了完結。

於是,WeakHashMap提供了一種機制,去清除對應的這種情況下的Entry。並在主要方法調用路徑中,會執行expungeStaleEntries方法。

/**
* Expunges stale entries from the table.
*/
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}

expungeStaleEntries首先從引用隊列中去一個取出對應的引用對象,實際類型即為Entry。然後找map中找到對應的Entry,並從map中移除。

為了將上述情況中的keynull,與直接向map中put一個key本身就為null區分開,WeakHashMapput時,會將keynull轉成成一個new Object()對象。並以此為keyput到map中。

/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for this key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @return the previous value associated with <tt>key</tt>, or
*         <tt>null</tt> if there was no mapping for <tt>key</tt>.
*         (A <tt>null</tt> return can also indicate that the map
*         previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
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;
}

關鍵語句maskNull(key);實現如下:

/**
* Value representing null keys inside tables.
*/
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;
}

當然了,取一個指定的keynullEntry也會相應轉化。

總結一下,WeakHashMap中的Entry,實際上是一個弱引用對象,使得key成為了事實上的referent,具備了弱引用特性。實際使用中,WeakHashMap中元素項的key,往往是指向具有一定生命週期的目標對象實體。如Activity作為key,等等,這需要實際考慮具體的業務場景。

3.4 ThreadLocal

ThreadLocal為多線程場景下的共享變量的線程安全,提供了一種方案。具體思路是將共享變量,分別放到各自線程內部的ThreadLocalMap屬性中。ThreadLocalMap,以ThreadLocal對象為key,對應需要存入的對象為value,對外,統一封裝在ThreadLocal類內部,並提供接口。也就是說,外界對ThreadLocalMap是無感知的。

ThreadLocal對外主要提供了T get()set(T value)remove()方法。

/**
* Returns the value in the current thread's copy of this
* thread-local variable.  If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value.  Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
*        this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Removes the current thread's value for this thread-local
* variable.  If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim.  This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

上述方法最終都轉到了ThreadLocalMap中。ThreadLocalMap內部是數組存儲的數據結構,在必要時候進行擴容。重點看一下元素項Entry的定義:

/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal oject).  Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table.  Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

我們發現,與WeakHashMap類似,ThreadLocalMap中的Entry繼承的也是WeakReferenceEntry中的key即為referent,指向的目標對象實體ThreadLocal對象。因此,Entry中的key具備了弱引用特性。

當所指向的ThreadLocal對象生命週期完結時,Entry中的key會自動被置為null,同時,與WeakHashMap類似,ThreadLocalMap中也提供了expungeStaleEntry去清除對應的Entry

其中具體的引用關係如下圖所示。

Java/Android中的引用類型及WeakReference應用實踐

如此,對於線程池等線程複用的場景,即使線程對象依然存活,ThreadLocal對象也不會發生內存洩露,會隨著其本身生命週期的終結而終結。

3.5 LifeCycle

最新的Android jetpack套件中,LifeCycle是其中重要的一個組成部分。LifeCycle提供了一種對象可以觀察組件的聲明週期的機制,並在源碼層面開始支持。整體設計上,採用的是觀察者模式,具有生命週期的被觀察的組件,是被觀察者,觀察組件生命週期的對象,是觀察者。組件針對不同的生命週期的變化,會發出對應的事件,並對應回調觀察者對象的中相應的方法。

ComponentActivity為例,源碼中直接實現了LifecycleOwner接口,並初始化了mLifecycleRegistry對象。LifecycleRegistry,作為觀察者與被觀察者的橋樑,主要完成對觀察者的註冊,並接收到被觀察者發出的事件後,分發給觀察者。

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
....
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
....

LifecycleRegistry中,存在mLifecycleOwner屬性,此對象是一個弱引用對象,其referent指向的目標對象實體LifecycleOwner,即被觀察者。對應註釋部分如下:

/**
* The provider that owns this Lifecycle.
* Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
* the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
* because it keeps strong references on all other listeners, so you'll leak all of them as
* well.
*/
private final WeakReference<LifecycleOwner> mLifecycleOwner;
....

也就是說,LifecycleRegistry對象中對被觀察者,即擁有聲明週期的組件,如Activity、Fragment,不是直接強引用的,而是通過,mLifecycleOwner,去弱引用,防止LifecycleRegistry在被洩露的情況下導致組件被進一步洩露。

3.6 LeakCanary實現原理

LeakCanary,作為Android中知名的內存洩露檢測工具,能夠檢測使用過程中洩露的對象,並提供詳細的路徑等信息。

LeakCanary主要實現原理是通過WeakReference去弱引用到目標對象,並結合ReferenceQueue以實現檢測到目標對象生命週期的目的。下面以檢測Activity為例,分析LeakCanary監測過程。

執行LeakCanary.install(context);後,會執行RefWatcher的構建。

public final class LeakCanary {
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
....
}

其中,buildAndInstall()方法中,會自動加上對Activity以及Fragment的監測。Activity對應的監測類是ActivityRefWatcher

/**
* Creates a {@link RefWatcher} instance and makes it available through {@link
* LeakCanary#installedRefWatcher()}.
*
* Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.
*
* @throws UnsupportedOperationException if called more than once per Android process.
*/
public RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}

ActivityRefWatcher中,通過向Application中註冊Activity的生命週期回調接口,並在Activity onActivityDestroyed方法回調中,開始觀察。

public final class ActivityRefWatcher {
private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.refWatcher.watch(activity);
}
};
....
}

watch方法中,開始對Activity對象增加上弱引用。

public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}

KeyedWeakReference,繼承WeakReference。構造器中,形成referent對Activity對象的弱引用特性,並傳入了引用隊列

final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}

ensureGoneAsync方法中,會調用ensureGone觸發GC。

public interface GcTrigger {
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}
private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
void runGc();
}

隨後通過判斷引用隊列中是否有此引用對象,去判斷Activity對象是否被回收。並針對未回收情況,通過HeapDump去分析內存及堆棧詳情。

對其他對象,如Fragment等的內存洩露監測,基本過程也是相似的。

四、結語

WeakReference自身的特性,決定了可以被廣泛的應用到實際的需求場景中,釐清WeakReference中對應的各概念,尤其是其內部的T referent,可以進一步加深對WeakReference的理解。並在對應的場景中,選擇對應現有的,或自實現相應的類結構,以完成目標功能的同時,減少不必要的內存洩露等問題。

end~

相關文章

大廠面試中遇到的幾十道webpack與react面試題

從業務場景角度談數組去重

在iOS中使用OpenGLES實現繪畫板

你真的會用簡單工廠模式嗎?