LeakCanary 源码分析(上)

最近在做一个功能,涉及到了LeakCanary,因为使用比较简单,没有任何技术含量,就去了解了解原理。然后我的感受是,很神奇,很神奇。

准备分两部分来对其解说。第一部分是查找内存泄漏对应Reference的原理;第二部分是找到内存泄漏的Reference之后,如何查找对应的引用链。

附上LeakCanary源码

总体流程:

install()----->AndroidRefWathcherBuilder「构建者模式,构建观察者RefWatcher,buildAndInstall--(build RefWatcher 和对Activity和Fragment做install)」 -------->在ActivityRefWatcher中install做注册「Application的registerActivityLifecyclerCallBacks的  在监听中onActivityDestory,进行refWatcher.watch。」------>在watch中,用UUID做唯一key标识,将Reference添加到KeyedWeakReference,做软引用----->ensureGone

简单的说就是:

在Activity对象onDestory的时候,新见一个WeakReference对象指向Activity对象,如果Activity对象被垃圾回收的话,WeakReference对象就会进入引用序列的ReferenceQueue。所以我们只需要在Activity对象OnDestory之后去查看ReferenceQueue序列时候有该WeakReference对象即可。第一次观察是Activity的onDestory5秒后,如果发现ReferenceQueue对来还没有WeakReference对象,就进入第二次观察,如果有了,就证明没有泄漏,第二次观察跟第一次观察相比区别在于会先进行垃圾回收,在进行ReferenceQueue序列的观察。

涉及到知识:WeakReference、ReferenceQueue。

在Java中,当对象o被创建的时候,是放在Heap中。当GC运行的时候,如果发现没有任何引用指向O,O就会被回收,腾出内存空间。一个对象被回收,必须满足两个条件。1)没有任何引用指向他,2)GC被运行

例如:

我们通常是把某个对象的reference设置为null来保证这个对象在下次GC的时候被回收。

ModelChapterContentReq req = new ModelChapterContentReq();
req = null;

但是,手动置空度对象对我们来说,真的是不太合适。对于简单的情况,不需要我们置空对象,

当调用他的方法执行完毕后,指向他的引用也会被从stack中popup,所以他就能在下一次的GC中回收了。但是也有特殊情况,在使用cache的时候,由于Cache的对象正是运行程序所需要的,那么只要程序正在运行,Cache中的引用就不会被GC(Cache中的引用拥有了有主程序一样的生命周期)。那么随着Cache中的引用越来越多,GC无法回收的O也越来越多,无法被自动回收, 当这些object需要被回收时, 回收这些object的任务只有交给程序编写者了. 然而这却违背了GC的本质(自动回收可以回收的objects).

这时候WeakReference出现了,当一个对象被WeakReference指向的时候,没有任何其引用指向的时候,如果GC,那这个对象就会被回收。

ModelChapterContentReq req = new ModelChapterContentReq();
WeakReference reference = new WeakReference(req);

WeakReference的一个特点是他何时被回收是不确定的,这是有GC的运行不确定的时候导致的,所以一般WeakReference引用的对象是有价值被Cache的,而且很容易被构建,且很消耗内存的对象。

ReferenceQueue:

在WeakReference在被回收之后,WeakReference其实本身也就没有用了,Java提供了一ReferenceQueue来保存已经被回收的Reference, 用法是在定义WeakReference的时候将一个ReferenceQueue的对象作为参数传入构造函数.

public void referenceTest(){
    Test test = new Test();//test---强引用
    ReferenceQueue queue = new ReferenceQueue();//引用序列
    WeakReference weak = new WeakReference(test, queue);//弱引用
    test = null;
    Runtime.getRuntime().gc();//调用系统GC
    System.runFinalization();//强制系统回收已经没有强引用的对象
    Reference poll = null;
    while ((poll = queue.poll()) != null) {
        Log.i(TAG,"Reference"+poll.toString());
    }

在这段代码中,把强引用test复制为null,则对象只有weak这个弱引用在。之后手动触发GC,日志中可以看到test的弱引用已经放到了引用队列中,说明Tes对象已经被回收。LeakCanary原理「1」利用这个原理初步定位内存泄漏对象,『2』在调用系统接口dump出堆转储文件快照.hprof,「3」调用haha库分析该文件解析出最短引用路径。

关于GC 补充的一个知识点:

对于JVM GC,里面涉及到的知识比较复杂。我就直接表达理论,你调用的GC,不一定会真正的去GC,尤其是在在Android5.0之后,你调用GC,没太大的作用。

public static void gc() {
        boolean shouldRunGC;
        synchronized (LOCK) {
            shouldRunGC = justRanFinalization;
            if (shouldRunGC) {
                justRanFinalization = false;
            } else {
                runGC = true;
            }
        }
        if (shouldRunGC) {
            Runtime.getRuntime().gc();
        }
    }

 

就是在GC的前面做了很多处理,结果就是只有shouldRunGC等于true的时候,才会去进行JVM GC。

 

LeakCanary.install();

  public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

在refWatcher中,

 

 

 

 

 

参考掘金

你可能感兴趣的:(LeakCanary 源码分析(上))