zoukankan      html  css  js  c++  java
  • 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
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    【学习】Filter
    黑马程序员JAVA基础String 类(上)
    黑马程序员JAVA基础基本数据类型对象包装类
    [xcode]安装xcode出现一个错误:The Installation Failed.
    [SW]SolidWorks文件属性(Properties)
    AutoCAD:使用VS2012调试AutoCAD 2010中.Net DLL类库
    AutoCAD:ObjectARX所有版本下载地址
    [IOS]如何让手上的 iPhone 或 iPad 进入 DFU 或 Recovery 模式
    [iOS]Win8下iTunes无法连接iPhone版本的解决方法
    [SW]SolidWorks API SDK二次开发学习心得01开发方式
  • 原文地址:https://www.cnblogs.com/lwbqqyumidi/p/13021441.html
Copyright © 2011-2022 走看看