一、背景
一般意义上而言,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 这个引用变量,依据实际的变量定义的位置,有可能分配在栈中存储(如在方法中定义),也有可能分配在堆中存储(如作为类的字段)。
引用关系画一个简单的图,大概如下所示:
?
现实中,对同一个对象实体,往往会具有复杂的多个引用指向,如最常见的将对象的引用变量作为实参传递,形参接收后会指向同一对象实体等等。因此,现实中的对象引用与实体关系比较复杂,可能如下:
?
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 类本身,是一个抽象类,作为具体引用类型的基类,定义了基本的类属性与行为。主体类结构如下所示:
?
从类的注释上可以看出,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 属性为null ,get() 方法将返回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 是一个抽象类,实际使用时,外部用的是其具体的子类,依据实际的需求场景,对应选择使用WeakReference 、SoftReference 和PhantomReference 。
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对象尚未进入垃圾回收流程com.corn.javalib.A@60e53b93
a对象尚未进入垃圾回收流程com.corn.javalib.A@60e53b93
引用对象:null
复制代码
当a对象没有强引用可达时,只有软引用可达,此时,无论系统是否发生GC,a对象的生命周期依然是存活的,不会被垃圾回收。也正因为如下,引用队列 中是不存在对应的srA这个引用对象 的。
上述过程对A这个类型的目标对象实体 的引用关系,起始是这样的:
?
当执行a = null 时,此时引用关系如下:
?
强引用断裂,但不影响引用对象 中的对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");
}
}
}
复制代码
输出结果为:
引用对象:java.lang.ref.WeakReference@60e53b93
a对象尚未进入垃圾回收流程com.corn.javalib.WeakReferenceTest$A@5e2de80c
in A finalize
a对象进入垃圾回收流程
引用对象:java.lang.ref.WeakReference@60e53b93
复制代码
示例代码中,System.gc(); 执行后,之所以让当前线程sleep(1) ,是基于进一步确保GC线程能被调度执行考虑的。最终的输运行结果,对应的弱引用对象 ,被入队到引用队列中 ,表明A对象实体已经被垃圾回收。
引用关系起初是这样的:
?
执行a = null 时,此时引用关系如下:
?
当虚拟机GC时,首先会将referent 置为null ,引用关系变为如下:
?
此时,A对象实体已经变成“孤魂野鬼”,可以被垃圾回收。GC过程中,弱引用对象 入队引用队列 。
?
由此,我们发现,弱引用一个强大的地方在于,弱引用本质上,是不改变目标对象实体 的生命周期的,也不影响目标对象实体 被GC的时机,并且,还提供了一种机制,即基于引用队列 下的,可以直接去监测目标对象实体 是否已经被GC。
这无疑是相当强大的,相当于提供了一种可以监测到对象是否被GC的方法,且不影响到对象生命周期本身。
2.5 虚引用
无论是SoftReference 、WeakReference 还是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特性总结
相比SoftReference 和PhantomReference ,WeakReference 应用更加普遍。主要得益于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
WeakHashMap 与HashMap 基本实现过程是一样的,根本的区别在于,其内部的Entry 继承的是WeakReference ,Entry 中的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();
}
}
复制代码
因此,当Entry 中key 指向的目标对象实体 本身没有其他强引用或软引用可达时,GC发生时,此目标对象实体 会被收回,Entry 中key 会被置为null ,并且,此Entry 对象,将被入队到引用队列 中。但直到此时,对于WeakHashMap 而言,这些key 为null 的Entry 还是作为一个个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中移除。
为了将上述情况中的key 为null ,与直接向map中put 一个key 本身就为null 区分开,WeakHashMap 在put 时,会将key 为null 转成成一个new Object() 对象。并以此为key ,put 到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;
}
复制代码
当然了,取一个指定的key 为null 的Entry 也会相应转化。
总结一下,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 继承的也是WeakReference ,Entry 中的key 即为referent ,指向的目标对象实体 为ThreadLocal 对象。因此,Entry 中的key 具备了弱引用特性。
当所指向的ThreadLocal 对象生命周期完结时,Entry 中的key 会自动被置为null ,同时,与WeakHashMap 类似,ThreadLocalMap 中也提供了expungeStaleEntry 去清除对应的Entry 。
其中具体的引用关系如下图所示。
?
如此,对于线程池等线程复用的场景,即使线程对象依然存活,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 的理解。并在对应的场景中,选择对应现有的,或自实现相应的类结构,以完成目标功能的同时,减少不必要的内存泄露等问题。
|