美文网首页
LeakCanary源码阅读

LeakCanary源码阅读

作者: 四喜汤圆 | 来源:发表于2018-09-23 20:57 被阅读20次

一、相关概念

1. 弱引用与引用队列

在WeakReference指向的对象在GC时被回收后,WeakReference本身其实也就没有用了,系统会把该弱引用对象加入到与之关联的ReferenceQueue中(当然前提是你创建该WeakReference的时候给其关联了ReferenceQueue)。


摘自 零度anngle 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/zmx729618/article/details/54093532?utm_source=copy

二、使用

  1. gradle中添加依赖
dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
}

leakcanary-android-no-op是用于release版本的,因为LeakCanary会不断GC,造成stop the world 这在release版本中时不允许的,因此内存泄漏检测我们只能用在debug版本。

  1. 在Application中进行初始化操作
public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

LeakCanary在主进程中检测内存泄漏,在另外的进程中分析hprof文件。多进程下Application被多次创建,为了防止在 Application中出现无用的重复初始化,在Application中可通过processName来过滤,只有在指定进程才做相应的初始化

  1. 在Manifest中配置Application
<application
        android:name=".ExampleApplication ">
....</application>

三、源码分析

3.1LeakCanary的准备工作

LeakCanary检测内存泄漏的入口:Application中的

LeakCanary.install(this);

该方法的主要作用是通过Builder模式构建RefWatcher实例,并做一些初始化的准备工作(为application注册ActivityLifecycleCallback)

public final class LeakCanary{
    public static RefWatcher install(Application application){
        // 采用构造器模式构造RefWatcher实例,链式调用
            return ((AndroidRefWatcherBuilder)refWatcher(application)
            .listenerServiceClass(DisplayLeakService.class)
            .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()))
            .buildAndInstall();
    }
public static AndroidRefWatcherBuilder refWatcher(Context context) {
        return new AndroidRefWatcherBuilder(context);
    }
}

1)得到AndroidRefWatcherBuilder

上述方法采用构建者模式,(AndroidRefWatcherBuilder extends RefWatcherBuilder)最终构造出一个RefWatcher实例,RefWatcher用来监视一个引用的可达情况

2).listenerServiceClass(DisplayLeakService.class)

public final class AndroidRefWatcherBuilder{
    
    public AndroidRefWatcherBuilder listenerServiceClass(Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
        return (AndroidRefWatcherBuilder)this.heapDumpListener(new ServiceHeapDumpListener(this.context, listenerServiceClass));
    }
}

上述AbstractAnalysisResultService是一个抽象类,继承自IntentServicepublic abstract class AbstractAnalysisResultService extends IntentService),用于处理heapDump结果。
LeakCanary#install()中传入的参数DiaplayLeakService继承自AbstractAnalysisResultServicepublic class DisplayLeakService extends AbstractAnalysisResultService),运行在独立的进程中

 <service
        android:name=".DisplayLeakService"
        android:process=":leakcanary"
        android:enabled="false"
        />

3)excludedRefs(...)

有时候我们需要忽略一些特殊情况下的内存泄漏比如SDK或者制造商导致的内存泄漏,这些内存泄漏我们并不需要显示出来,那么刚好AndroidExcludedRefs这个枚举就派上用场了。

4)buildAndInstall()

public final class AndroidRefWatcherBuilder{

    public RefWatcher buildAndInstall() {
        RefWatcher refWatcher = this.build();
        if(refWatcher != RefWatcher.DISABLED) {
            LeakCanary.enableDisplayLeakActivity(this.context);
            // 调用ActivityRefWatcher.install()方法,将Application的上下文和RefWatcher传入
            ActivityRefWatcher.install((Application)this.context, refWatcher);
        }
        return refWatcher;
    }
}

构建出RefWatcher实例,紧接着调用ActivityRefWatcher.install((Application)this.context, refWatcher);这是重点

public final class ActivityRefWatcher{
    // ActivityLifecycleCallbacks:应用内Activity生命周期发生变化时会回调相应函数【该方法在API14后才有】
    private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        public void onActivityStarted(Activity activity) {
        }

        public void onActivityResumed(Activity activity) {
        }

        public void onActivityPaused(Activity activity) {
        }

        public void onActivityStopped(Activity activity) {
        }

        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }

        public void onActivityDestroyed(Activity activity) {
            ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
    };
    void onActivityDestroyed(Activity activity) {
        this.refWatcher.watch(activity);
    }
    // install
    public static void install(Application application, RefWatcher refWatcher) {
        (new ActivityRefWatcher(application, refWatcher)).watchActivities();
    }
    // 构造函数
    public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
        this.application = (Application)Preconditions.checkNotNull(application, "application");
        this.refWatcher = (RefWatcher)Preconditions.checkNotNull(refWatcher, "refWatcher");
    }
    public void watchActivities() {
        this.stopWatchingActivities();
        // 为application注册ActivityLiftcycleCallbacks,监听应用内所有Activity的生命周期
        this.application.registerActivityLifecycleCallbacks(this.lifecycleCallbacks);
    }

    public void stopWatchingActivities() {
        // 为application取消注册ActivityLiftcycleCallbacks监听
        this.application.unregisterActivityLifecycleCallbacks(this.lifecycleCallbacks);
    }
}

上面的代码逻辑其实很简单,就是为application注册ActivityLifecycleCallback,监听应用内所有Activity的生命周期,LeakCanary只关注onDestroy(),在Activity的onDestroy()回调时去做一些事情。

3.2 LeakCanary检测是否发生了内存泄漏

当Activity回调了生命周期中的onDestory()时,LeakCanary就运行下面的代码检测是否发生内存泄漏

public final class  RefWatcher{
    
    private final Set<String> retainedKeys;
    // 引用队列
    private final ReferenceQueue<Object> queue;
    
    public void watch(Object watchedReference) {
        this.watch(watchedReference, "");
    }

    public void watch(Object watchedReference, String referenceName) {
        if(this != DISABLED) {
            // 判空,如果是null的话抛出NullPointerException异常
            Preconditions.checkNotNull(watchedReference, "watchedReference");
            Preconditions.checkNotNull(referenceName, "referenceName");
            // 得到当前时间:作为监视开始时间
            long watchStartNanoTime = System.nanoTime();
            // 随机生成UUID,并添加到Set<String>中
            String key = UUID.randomUUID().toString();
            this.retainedKeys.add(key);
            // 传入引用队列:监听GC后的情况
            KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
            // 这个方法是LeakCanary检测对象是否被回收的核心代码
            this.ensureGoneAsync(watchStartNanoTime, reference);
        }
    }
}


final class KeyedWeakReference extends WeakReference<Object> {
    public final String key;
    public final String name;

    /**
    * @param referent :当前被监视的引用
    * @param key:当前被监视对象的key(唯一UUID)
    * @param name:引用name
    * @param referenceQueue:引用队列
    */
    KeyedWeakReference(Object referent, String key, String name, ReferenceQueue<Object> referenceQueue) {
        super(Preconditions.checkNotNull(referent, "referent"), (ReferenceQueue)Preconditions.checkNotNull(referenceQueue, "referenceQueue"));
        this.key = (String)Preconditions.checkNotNull(key, "key");
        this.name = (String)Preconditions.checkNotNull(name, "name");
    }
}

/**
* 弱引用
*/
public class WeakReference<T> extends Reference<T> {

    /**
     * Creates a new weak reference that refers to the given object.  The new
     * reference is not registered with any queue.
     *
     * @param referent object the new weak reference will refer to
     */
    public WeakReference(T referent) {
        super(referent);
    }

    /**
     * Creates a new weak reference that refers to the given object and is
     * registered with the given queue.
     *
     * @param referent object the new weak 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 WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

首先

  1. 将被监视的对象用弱引用KeyedWeakReference包装起来,包括
  • String key:唯一的UUID
  • String name:被监视的对象的referenceName
  • Object reference:被监视的对象
  • ReferenceQueue<Object> queue:引用队列
  1. 将该引用对应的唯一的UUID放入Set<String> retainedKey

然后:再调用ensureGoneAsync检查是否发生内存泄漏

public final class  RefWatcher{
    // 真正实现WatchExecutor接口的是AndroidWatchExecutor类,
    // 在这个类里面给主线程的消息队列添加一个IdleHandler,
    // 当主线程空闲没有消息的时候就执行execute()方法,
    // 而在上面的execute()方法里面则调用ensureGone()方法。
    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        this.watchExecutor.execute(new Retryable() {
            public Result run() {
                return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
            }
        });
    }
    Result ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
        // 将引用尝试从queue中poll出来
        this.removeWeaklyReachableReferences();
        if(this.debuggerControl.isDebuggerAttached()) {
            // 规避调试模式
            return Result.RETRY;
        } else if(this.gone(reference)) {
            // 检测引用是否已经被回收
            return Result.DONE;
        } else {
            // 手动GC
            this.gcTrigger.runGc();
            // 再次尝试poll,检测是否被回收
            this.removeWeaklyReachableReferences();
            if(!this.gone(reference)) {
                // 还没有被回收,则dump堆信息,调起分析进程分析
                long startDumpHeap = System.nanoTime();
                long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
                File heapDumpFile = this.heapDumper.dumpHeap();
                if(heapDumpFile == HeapDumper.RETRY_LATER) {
                    return Result.RETRY;
                }

                long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
                this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this.excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
            }

            return Result.DONE;
        }
    }

    private boolean gone(KeyedWeakReference reference) {
        return !this.retainedKeys.contains(reference.key);
    }

    // 检查队列中是否已包含该object的弱引用对象
    private void removeWeaklyReachableReferences() {
        KeyedWeakReference ref;
        while((ref = (KeyedWeakReference)this.queue.poll()) != null) {
            // 引用从引用队列中出队的同时,也将retainedKeys中引用对应的key值移除
            this.retainedKeys.remove(ref.key);
        }

    }
}

需要注意的点:

1. 真正实现WatchExecutor接口的是AndroidWatchExecutor类,执行WatchExecutor的execute()方法

上面代码执行WatchExecutor的execute()方法,真正实现WatchExecutor接口的是AndroidWatchExecutor类,在这个类里面给主线程的消息队列添加一个IdleHandler,当主线程空闲没有消息的时候就执行execute()方法,而在上面的execute()方法里面则调用ensureGone()方法。

2. 如何判断该对象是否被回收

通过判断retainedKey中是否存在该引用对应的key。不存在,说明该对象已被回收【因为在Activity回调onDestroy()时,LeakCanary将该对象的keyUUID添加到了retainedKey中,在removeWeaklyReachableReferences()方法中尝试将引用队列中的所有引用出队,同时把retainedKey中对应的key移除】

如何检测内存泄漏

3. 当判断发生内存泄漏后所做的操作

dump heap并生成一个hprof文件并解析这个文件,得到泄漏对象到GC根的最短路径,得到这个路径后发一个Notification展示出来即可。

未完待续............

四、LeakCanary工作机制总结

1.RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。

2.然后在后台线程检查引用是否被清除,如果没有,调用GC。

3.如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。

4.在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。

5.得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。

6.HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。

7.引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。

参考文献

Java内存问题 及 LeakCanary 原理分析
利用 LeakCanary 来检查 Android 内存泄漏
三叶梧桐_LeakCanary原理详解
校招指南

相关文章

  • LeakCanary源码阅读

    一、相关概念 1. 弱引用与引用队列 在WeakReference指向的对象在GC时被回收后,WeakRefere...

  • LeakCanary 2.0源码分析与总结

    本文基于LeakCanary 2.0源码分析LeakCanary - 官方地址LeakCanary - GitHu...

  • LeakCanary源码解析

    开源库路径https://github.com/square/leakcanary 源码结构 leakcanary...

  • LeakCanary 原理分析

    本文主要内容 1、Reference 简介 2、LeakCanary 使用 3、LeakCanary 源码分析 L...

  • LeakCanary原理

    一、源码 LeakCanary内存分析模块,独立进程,包名:leakcanary,保持app进程独立。在Appli...

  • LeakCanary1 源码阅读

    写在前面 本篇文章只是作者对LeakCanary源码阅读后的读后感和收货,并没有大段大段的粘贴代码,并且感觉这样也...

  • 监测内存泄漏--LeakCanary源码分析

    1 在全局Application类中注册: LeakCanary.install(this);点进去看源码: 源码...

  • LeakCanary源码解析

    LeakCanary源码解析 前言 对于内存泄漏的检测,基于MAT起点较高,所以一般我们都使用LeakCanary...

  • LeakCanary源码解析

    LeakCanary源码解析 内存泄露 今天来讲解一下老生常谈的问题了,内存泄露以及讲解LeakCanary是如果...

  • LeakCanary内存泄露检测原理

    LeakCanary代码量比较多,阅读源码容易把人绕晕,提取主干代码,精简后的代码只有200行,看完这200行代码...

网友评论

      本文标题:LeakCanary源码阅读

      本文链接:https://www.haomeiwen.com/subject/swvznftx.html