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属性为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对象尚未进入垃圾回收流程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 虚引用

无论是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

其中具体的引用关系如下图所示。

 

技术图片

 

如此,对于线程池等线程复用的场景,即使线程对象依然存活,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应用实践的主要内容,如果未能解决你的问题,请参考以下文章

02-JAVA基础及面向对象(补充)

javascript原始值和引用值类型及区别

布尔类型及引用

js中变量作用域及内存

JavaScript面向对象—变量及作用域和内存问题

实验十——一维数组的定义及引用