转载

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 这个引用变量,依据实际的变量定义的位置,有可能分配在栈中存储(如在方法中定义),也有可能分配在堆中存储(如作为类的字段)。

引用关系画一个简单的图,大概如下所示:

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对象尚未进入垃圾回收流程com.corn.javalib.A@60e53b93
a对象尚未进入垃圾回收流程com.corn.javalib.A@60e53b93
引用对象: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");
        }

    }
}
复制代码

输出结果为:

引用对象: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对象实体已经被垃圾回收。

引用关系起初是这样的:

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~

原文  https://juejin.im/post/5e0967fc6fb9a016253c20e0
正文到此结束
Loading...