Java/Android中的引用类型及WeakReference应用实践
Posted HappyCorn
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java/Android中的引用类型及WeakReference应用实践相关的知识,希望对你有一定的参考价值。
一、背景
一般意义上而言,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
的理解。并在对应的场景中,选择对应现有的,或自实现相应的类结构,以完成目标功能的同时,减少不必要的内存泄露等问题。
end~
作者:HappyCorn
链接:https://juejin.im/post/5e0967fc6fb9a016253c20e0
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
以上是关于Java/Android中的引用类型及WeakReference应用实践的主要内容,如果未能解决你的问题,请参考以下文章