LeakCanary的实现原理

115 阅读2分钟

1、举例

假设我们去某小区,那么门卫对我们的盘问就类似于LeakCanary

1、进门登记身份证。那么你则进入了保安观察列表

2、如果我们约定一次只能停留1个小时,那么我1小时后再看你是否登记出来了,如果还是没有,则把你加入怀疑列表

3、如果怀疑列表中的人大于5了,用大喇叭喊到你要出来了,然后保安就通知警察,看是不是坏人。

4、警察确认是坏人,则抓人。

2、实际

身份证:被观察对象的key(uuid)
保安:RefWatcher
观察列表:watchedReferences
怀疑列表:retainReferences
大喇叭:GC
警察:HAHA库

1、以activity为例,Activity调用onDestory后,UUID生成key。被KeyWeakRreference包装,和referenceQueue关联。那key和KeyWeakRreference。存到观察列表watchedReferences。(如果被引用的对象activity被回收则引用队列referenceQueue会收到该对象的弱应用KeyWeakRreference,以此判断对象是否被回收)

2、过了5秒后,调用moveToRetained方法,先判断是否释放了该activtiy,因为被释放了,则观察列表和怀疑列表都会清除它,如果过了这么些时间还是再观察列表里,如果没有释放,则从“观察列表”watchedReferences上升到“怀疑列表”retainReferences

3、如果retainReferences超过5,先调用GC,再请HAHA库去分析dump之后的heap内存

4、HAHA确认内存泄漏对象

3、图解

leakCanary流程图.png

4、代码

Watcher.java


 
package com.example.lib.leak;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;

public class Watcher {


    //观察列表
    private HashMap<String, KeyWeakReference> watchedReferences = new HashMap<>();
    //怀疑列表
    private HashMap<String, KeyWeakReference> retainedReferences = new HashMap<>();

    //引用队列,相当于一个监视器设备,所有需要监视的对象,盛放监视对象的容器 都与之关联
    //当被监视的对象被gc回收后,对应的容器就会被加入到queue
    private ReferenceQueue queue = new ReferenceQueue();

    public Watcher() {
    }

    /**
     * 清理观察列表和怀疑列表的引用容器
     */
    private void removeWeaklyReachableReferences() {
        System.out.println("清理列表...");
        KeyWeakReference findRef = null;

        do {
            findRef = (KeyWeakReference) queue.poll();
            //不为空说明对应的对象被gc回收了,那么可以把对应的容器从观察列表,怀疑列表移除
            System.out.println("findRef = " + findRef);
            if (findRef != null) {
                System.out.println("打印对应的对象的key: " + findRef.getKey());
                //根据key把观察列表中对应的容器移除
                Reference removedRef = watchedReferences.remove(findRef.getKey());
                //如果removedRef为空,那么有可能被放入到怀疑列表了
                //TODO: 思考什么情况下会出现这么现象?
                //那么尝试从怀疑列表中移除
                if (removedRef == null) {
                    retainedReferences.remove(findRef.getKey());
                }
            }
        } while (findRef != null);//把所有放到referenceQueue的引用容器找出来
    }

    /**
     * 根据key把对应的容器加入到怀疑列表
     *
     * @param key
     */
    private synchronized void moveToRetained(String key) {
        System.out.println("加入到怀疑列表...");
        //在加入怀疑列表之前,做一次清理工作
        removeWeaklyReachableReferences();
        //根据key从观察列表中去找盛放对象的容器,如果被找到,说明到目前为止key对应的对象还没被是否
        KeyWeakReference retainedRef = watchedReferences.remove(key);
        if (retainedRef != null) {
            //把从观察列表中移除出来的对象加入到怀疑列表
            retainedReferences.put(key, retainedRef);
        }
    }


    public void watch(Object watchedReference, String referenceName) {
        System.out.println("开始watch对象...");
        //1. 在没有被监视之前,先清理下观察列表和怀疑列表
        removeWeaklyReachableReferences();

        //2. 为要监视的对象生成一个唯一的uuid
        //相当于把要监视的对象 和容器 与 引用队列建立联系
        final String key = UUID.randomUUID().toString();
        System.out.println("待监视对象的key: " + key);
        //3. 让watchedReference 与一个KeyWeakReference建立一对一映射关系,并与引用队列queue关联
        KeyWeakReference reference = new KeyWeakReference(watchedReference, queue, key, "");

        //4. 加入到观察列表
        watchedReferences.put(key, reference);

        //5.过5秒后去看是否还在观察列表,如果还在,则加入到怀疑列表
        Executor executor = Executors.newSingleThreadExecutor();
        executor.execute(new Runnable() {
            @Override
            public void run() {
                Utils.sleep(5000);
                Watcher.this.moveToRetained(key);
            }
        });

    }

    public HashMap<String, KeyWeakReference> getRetainedReferences() {
        retainedReferences.forEach(new BiConsumer<String, KeyWeakReference>() {
                                       @Override
                                       public void accept(String key, KeyWeakReference keyWeakReference) {
                                           System.out.println("key: " + key + " , obj: " + keyWeakReference.get() + " , keyWeakReference: " + keyWeakReference);
                                       }
                                   }
        );
        return retainedReferences;
    }
} 

、、、、、、、、、、、、、、、、、、、、、、、

Utils

package com.example.lib.leak;

public class Utils {


    public static void sleep(long millis){
        System.out.println("sleep: " + millis);
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void gc(){
        System.out.println("执行gc...");
        //主要这里不是使用System.gc,因为它仅仅是通知系统在合适的时间进行一次垃圾回收操作
        //实际上并不保证一定执行
        Runtime.getRuntime().gc();
        sleep(100);
        System.runFinalization();
    }


}

KeyWeakReference

package com.example.lib.leak;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

/**
 * 继承自WeakReference,并且加入一个key,用来通过可以key可以查找到对应的KeyWeakReference
 * @param <T>
 */
public class KeyWeakReference<T> extends WeakReference<T> {

    private String key;
    private String name;

    public KeyWeakReference(T referent, String key, String name) {
        super(referent);
        this.key = key;
        this.name = name;
    }

    public KeyWeakReference(T referent, ReferenceQueue<? super T> q, String key, String name) {
        super(referent, q);
        this.key = key;
        this.name = name;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("KeyWeakReference{");
        sb.append("key='").append(key).append(''');
        sb.append(", name='").append(name).append(''');
        sb.append('}');
        return sb.toString();
    }
}

leakcanaryTest

package com.example.lib.leak;

public class leakcanaryTest {

    public static void main(String[] args) {

        Watcher watcher = new Watcher();

        Object obj = new Object();
        System.out.println("obj: " + obj);
        watcher.watch(obj,"");
          Utils.sleep(500);
          //释放对象
          obj = null;
          Utils.gc();
          //TODO: 思考如何判断被观察的对象可能存在泄漏嫌疑
    
          Utils.sleep(5000);
          System.out.println("查看是否在怀疑列表:" + watcher.getRetainedReferences().size());
    }

}