zoukankan      html  css  js  c++  java
  • 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分钟从入门到精通

  • 相关阅读:
    angularjs: ng-select和ng-options
    angularjs之$timeout指令
    angular的uiRouter服务学习(5) --- $state.includes()方法
    深究AngularJS——如何获取input的焦点(自定义指令)
    深究AngularJS——自定义服务详解(factory、service、provider)
    AngularJS 事件指令/input相关指令/样式指令/DOM操作指令详解
    字符串对象的创建
    redis安装和配置
    Cent Linux启动tomcat慢的问题
    Linux环境nginx的配置
  • 原文地址:https://www.cnblogs.com/wytiger/p/12938977.html
Copyright © 2011-2022 走看看