LeakCanary

一、简介

使用MAT来分析内存问题,有一些门槛,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比才能找到问题原因。 为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary

二、使用

在app build.gradle 中加入引用:

dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}

在 Application 中:

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

如果当前的进程是用来给LeakCanary 进行堆分析的则return,否则会执行LeakCanary的install方法。这样我们就可以使用LeakCanary了,如果检测到某个Activity 有内存泄露,LeakCanary 就会给出提示。

 
 

例子代码只能够检测Activity的内存泄漏,当然还存在其他类的内存泄漏,这时我们就需要使用RefWatcher来进行监控。改写Application,如下所示:

public class MyApplication extends Application {
    private RefWatcher refWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher= setupLeakCanary();
    }

    private RefWatcher setupLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return RefWatcher.DISABLED;
        }
        return LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher(Context context) {
        MyApplication leakApplication = (MyApplication) context.getApplicationContext();
        return leakApplication.refWatcher;
    }
}

install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。

最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。

public class OtherActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LeakThread leakThread = new LeakThread();
        leakThread.start();
    }

    class LeakThread extends Thread {
        @Override
        public void run() {
            try {
                CommonUtils.getInstance(OtherActivity.this);
                Thread.sleep(6 * 60 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = MyApplication.getRefWatcher(this);//1
        refWatcher.watch(this);
    }
}

MainActivity存在内存泄漏,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。

它用于自动监控Activity执行onDestroy方法之后是否发生内存泄露,当前此例onDestroy加是多余的,这里只是为了方便举例,如果想要监控Fragment,在Fragment中添加如上的onDestroy方法是有用的。

运行程序,这时会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来。

提示的notification:

 
 

使用小结
如果只关注activity的内存泄漏,那么在Application中onCreate加入LeakCanary.install(this);就OK了,

如果还关注fragment的泄漏情况,那么Application加上RefWatcher,然后在对应fragment页面中onDestroy中加入:

RefWatcher refWatcher = MyApplication.getRefWatcher(this);
     refWatcher.watch(this);

三、使用踩坑

在刚开始使用LeakCanary的时候,遇到了几个问题导致有内存泄漏发生时LeakCanary不发生通知,这里和大家分享一下。

1、 你的应用需要有写SD权限,因为LeakCanary需要生成hprof文件,保存在SD卡里面,因此你的应用要先申请权限

四、原理介绍

4.1 触发检测

每次当Activity/Fragment执行完onDestroy生命周期,LeakCanary就会获取到这个Activity/Fragment,然后初始化RefWatcher对它进行分析,查看是否存在内存泄漏。

4.2 判断是否存在内存泄漏

通过WeakReference + ReferenceQueue来判断对象是否被系统GC回收。

软引用和弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用或弱引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用或弱引用加入到与之关联的引用队列中。

首先尝试着从ReferenceQueue中获取待分析对象:如果不为空,那么说明正在被系统回收;如果直接就返回DONE,说明已经被系统回收了;如果没有被系统回收,可能存在内存泄漏,手动触发系统GC,然后再尝试移除待分析对象,如果还存在,说明存在内存泄漏。

4.3 分析内存泄漏

确定有内存泄漏后,调用heapDumper.dumpHeap()生成.hprof文件目录。HAHA 是一个由 square 开源的 Android 堆分析库,分析 hprof 文件生成Snapshot对象。Snapshot用以查询对象的最短引用链,找到最短引用链后,定位问题,排查代码将会事半功倍。

 
 

ensureGone 方法

我现在讲ensureGone方法的完整代码贴出来,我们逐行分析:

  ......
  // 移除所有弱引用可达对象,后面细讲
  removeWeaklyReachableReferences();
  ......
 
  // 上面执行 removeWeaklyReachableReferences 方法,判断是不是监视对象已经被回收了,如果被回收了,那么说明没有发生内存泄漏,直接结束
  if (gone(reference)) {
    return DONE;
  }
// 手动触发一次 GC 垃圾回收 gcTrigger.runGc();
// 再次移除所有弱引用可达对象 removeWeaklyReachableReferences();
// 如果对象没有被回收 if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); // 使用 Debug 类 dump 当前堆内存中对象使用情况 File heapDumpFile = heapDumper.dumpHeap(); // dumpHeap 失败的话,会走重试机制 if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); // 将hprof文件、key等属性构造一个 HeapDump 对象 HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key) .referenceName(reference.name) .watchDurationMs(watchDurationMs) .gcDurationMs(gcDurationMs) .heapDumpDurationMs(heapDumpDurationMs) .build(); // heapdumpListener 分析 heapDump 对象 heapdumpListener.analyze(heapDump); } return DONE; } 复制代码

看完上述代码,基本把检测泄漏的大致过程走了一遍,下面我们来看一些具体的细节。

(1). removeWeaklyReachableReferences 方法

removeWeaklyReachableReferences 移除所有弱引用可达对象是怎么工作的?

private void removeWeaklyReachableReferences() {
  KeyedWeakReference ref;
  while ((ref = (KeyedWeakReference) queue.poll()) != null) {
    retainedKeys.remove(ref.key);
  }
}

还记得我们在 refWatcher.watch 方法保存了当前监视对象的 ref.key 了么,如果这个对象被回收了,那么对应的弱引用对象会在回收时被添加到queue中,通过 poll 操作就可以取出这个弱引用,这时候我们从retainedKeys中移除这个 key, 代表这个对象已经被正常回收,不需要再被监视了。

(2).那么现在来看,判断这个对象是否被回收就比较简单了?
private boolean gone(KeyedWeakReference reference) {
  // retainedKeys 中不包含 reference.key 的话,就代表这个对象已经被回收了
  return !retainedKeys.contains(reference.key);
}

整体流程如下:

 
 

详细原理分析可以参考:LeakCanary原理分析

 

附:

LeakCanary 内存泄漏原理完全解析

LeakCanary,30分钟从入门到精通

posted @ 2020-05-22 18:42  tiger168  阅读(602)  评论(0编辑  收藏  举报