LeakCanary 内存泄漏原理完全解析

作者: 锐心凌志 | 来源:发表于2019-01-01 14:59 被阅读346次

    LeakCanary 的工作原理是什么?跟我一起揭开它的神秘面纱。

    一、 什么是LeakCanary

    LeakCanary 是大名鼎鼎的 square 公司开源的内存泄漏检测工具。目前上大部分App在开发测试阶段都会接入此工具用于检测潜在的内存泄漏问题,做的好一点的可能会搭建一个服务器用于保存各个设备上的内存泄漏问题再集中处理。

    本文首发于我的微信公众号:Android开发实验室,欢迎大家关注和我一起学Android,掉节操。

    二、 为什么要使用LeakCanary

    我们知道内存泄漏问题的排查有很多种方法, 比如说,Android Studio 自带的 Profile 工具、MAT(Memory Analyzer Tool)、以及LeakCanary。 选择 LeakCanary 作为首选的内存泄漏检测工具主要是因为它能实时检测泄漏并以非常直观的调用链方式展示内存泄漏的原因。

    三、 LeakCanary 做不到的(待定)

    虽然 LeakCanary 有诸多优点,但是它也有做不到的地方,比如说检测申请大容量内存导致的OOM问题、Bitmap内存未释放问题,Service 中的内存泄漏可能无法检测等。

    四、 LeakCanary 源码解析

    本章内容前后依赖关系强烈,建议顺序阅读。

    4.1 ActivityLifecycleCallbacks 与 FragmentLifeCycleCallbacks

    在开始 LeakCanary 原理解析之前,有必要简单说下 ActivityLifecycleCallbacks 与 FragmentLifeCycleCallbacks。

    // ActivityLifecycleCallbacks 接口
    public interface ActivityLifecycleCallbacks {
        void onActivityCreated(Activity var1, Bundle var2);
    
        void onActivityStarted(Activity var1);
    
        void onActivityResumed(Activity var1);
    
        void onActivityPaused(Activity var1);
    
        void onActivityStopped(Activity var1);
    
        void onActivitySaveInstanceState(Activity var1, Bundle var2);
    
        void onActivityDestroyed(Activity var1);
      }
    复制代码
    

    Application 类提供了 registerActivityLifecycleCallbacksunregisterActivityLifecycleCallbacks 方法用于注册和反注册 Activity 的生命周期监听类,这样我们就能在 Application 中对所有的 Activity 生命周期回调中做一些统一处理。

    public abstract static class FragmentLifecycleCallbacks {
    
        public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {}
    
        public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {}
    
        public void onFragmentDestroyed(FragmentManager fm, Fragment f) {}
    
        // 省略其他的生命周期 ...
      }
    复制代码
    

    FragmentManager 类提供了 registerFragmentLifecycleCallbacksunregisterFragmentLifecycleCallbacks 方法用户注册和反注册 Fragment 的生命周期监听类,这样我们对每一个 Activity 进行注册,就能获取所有的 Fragment 生命周期回调。

    4.2 LeakCanary 的使用

    4.2.1 使用方法

    我们直接在 Application 类中,添加一下代码即可。

    public class ExampleApplication extends Application {
      @Override public void onCreate() {
        super.onCreate();
        setupLeakCanary();
      }
    
      protected void setupLeakCanary() {
        // 启用严格模式
        enabledStrictMode();
        // 判断是否是 HeapAnalyzerService 所属进程
        if (LeakCanary.isInAnalyzerProcess(this)) {
          // This process is dedicated to LeakCanary for heap analysis.
          // You should not init your app in this process.
          return;
        }
        // 注册 LeakCanary
        LeakCanary.install(this);
      }
    
      private static void enabledStrictMode() {
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
            .detectAll() //
            .penaltyLog() //
            .penaltyDeath() //
            .build());
      }
    }
    复制代码
    
    <service
        android:name=".internal.HeapAnalyzerService"
        android:process=":leakcanary"
        android:enabled="false"
        />
    复制代码
    

    由于 LeakCanary 的核心 hropf 文件解析服务 HeapAnalyzerService 所属进程是与主进程独立的一个进程,所以在 setupLeakCanary中,我们需要排除其他进程,只对 leakcanary 进程注册 LeakCanary 监听处理。

    android:enabled="false" 这是什么? 这里简单说下,AndroidManifest文件中的 enabled 属性,可以看到 HeapAnalyzerService 这个组件默认是不可用的,所以如果在代码中动态启用这个组件,可以使用以下方法:

    public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
          boolean enabled) {
      ComponentName component = new ComponentName(appContext, componentClass);
      PackageManager packageManager = appContext.getPackageManager();
      int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
      // Blocks on IPC.
      packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
    }
    复制代码
    

    4.3 LeakCanary.install(this) 干了什么

    LeakCanary 的 install 方法实际上构造了一个 RefWatcher,

    /**
       * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
       * references (on ICS+).
       */
    public static @NonNull RefWatcher install(@NonNull Application application) {
      return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
          .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
          .buildAndInstall();
    }
    复制代码
    

    我们一个个来看这个注册方法。首先是 refWatcher 方法构造了一个 AndroidRefWatcherBuilder, 传入参数是当前Application 的 Context.

    public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
      return new AndroidRefWatcherBuilder(context);
    }
    复制代码
    

    listenerServiceClass 和 excludedRefs 方法是基于建造者模式传入分析Service 和 排除已知的泄漏问题 AndroidExcludedRefs,这里我就不贴代码了。

    重点看下 buildAndInstall 方法,这个方法很形象的表示将要进行建造者模式的最后一步 build 和 注册一些监听器,下面我们来看具体代码:

    public @NonNull RefWatcher buildAndInstall() {
      // 只允许 install 一次
      if (LeakCanaryInternals.installedRefWatcher != null) {
        throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
      }
      // 建造者模式的最后一步,构造对象
      RefWatcher refWatcher = build();
      // 判断是否开启了 LeakCanary,没有开启默认会返回 DISABLED 对象
      if (refWatcher != DISABLED) {
        // 手动开启 DisplayLeakActivity 组件,会在桌面上显示一个查看内存泄漏结果的入口
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
        // 是否检测 Activity 的 内存泄漏,默认开启
        if (watchActivities) {
          ActivityRefWatcher.install(context, refWatcher);
        }
        // 是否检测 Fragment 的 内存泄漏,默认开启
        if (watchFragments) {
          FragmentRefWatcher.Helper.install(context, refWatcher);
        }
      }
      // 复制给全局静态变量,防止二次调用
      LeakCanaryInternals.installedRefWatcher = refWatcher;
      return refWatcher;
    }
    复制代码
    

    以上代码作用大部分都在代码中注释了,剩下 ActivityRefWatcher.install 和 FragmentRefWatcher.Helper.install 方法没有注释。下面我们就来具体看看这两个方法究竟干了什么。

    (1). ActivityRefWatcher.install

    ActivityRefWatcher 的静态方法 install 获取到了当前 Application,然后添加了一个生命周期监听器 ActivityLifecycleCallbacks,这里的 lifecycleCallbacks 仅仅关注了 Activity 销毁的回调 onActivityDestroyed,在这里将传入的对象 activity 监听起来, refWatcher.watch(activity); 的具体代码我们稍后分析。

    public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
     Application application = (Application) context.getApplicationContext();
     ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    
     application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
    }
    
    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
     new ActivityLifecycleCallbacksAdapter() {
       @Override public void onActivityDestroyed(Activity activity) {
         refWatcher.watch(activity);
       }
    };
    复制代码
    

    (2). FragmentRefWatcher.Helper.install FragmentRefWatcher.Helper 的静态方法 install 里同样会注册一个 ActivityLifecycleCallbacks 用于监听 Activity 生命周期中的 onActivityCreated 的创建完成的回调,在 Activity 创建完成后,会对这个 Activity 注册 Fragment 的生命周期监听器。install 方法首先会判断系统是否大于等于 Android O, 如果是那么会使用 android.app.FragmentManager 进行注册,如果需要兼容 Android O 以下需要自行在依赖中添加对 leakcanary-support-fragment 组件的依赖,然后通过反射构造出SupportFragmentRefWatcher; 然后将fragmentRefWatchers所有监听器取出,在 Activity 创建完成后,添加 Fragment 的生命监听,主要关注 Fragment 的 onFragmentViewDestroyedonFragmentDestroyed 方法。具体代码如下:

    public static void install(Context context, RefWatcher refWatcher) {
          List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
          // 系统是否大于等于 Android O,如果是,添加 AndroidOFragmentRefWatcher
          if (SDK_INT >= O) {
            fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
          }
          // 如果添加了leakcanary-support-fragment的依赖,通过反射可以构造SupportFragmentRefWatcher
          try {
            Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
            Constructor<?> constructor =
                fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
            FragmentRefWatcher supportFragmentRefWatcher =
                (FragmentRefWatcher) constructor.newInstance(refWatcher);
            fragmentRefWatchers.add(supportFragmentRefWatcher);
          } catch (Exception ignored) {
          }
    
          if (fragmentRefWatchers.size() == 0) {
            return;
          }
    
          Helper helper = new Helper(fragmentRefWatchers);
          // 先监听 Activity 的创建完成回调
          Application application = (Application) context.getApplicationContext();
          application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
        }
    
        private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
          new ActivityLifecycleCallbacksAdapter() {
            @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
              // Activity 创建完成后,对Activity中的Fragment注册生命周期监听
              for (FragmentRefWatcher watcher : fragmentRefWatchers) {
                watcher.watchFragments(activity);
              }
            }
        };
    复制代码
    
    // AndroidOFragmentRefWatcher.java
    
    private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {
        // Fragment 中的View 视图销毁时
        @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
          View view = fragment.getView();
          if (view != null) {
            refWatcher.watch(view);
          }
        }
        // Fragment 销毁时
        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          refWatcher.watch(fragment);
        }
      };
    
    @Override public void watchFragments(Activity activity) {
      // 通过FragmentManager 注册 FragmentLifecycleCallbacks
      FragmentManager fragmentManager = activity.getFragmentManager();
      fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
    }
    复制代码
    

    上述流程我们已经完全搞清楚了,用一个流程图可以表示为:

    4.4 LeakCanary 内存泄漏检测原理

    从行文结构上,本小节应属于上一节后半部分内容,但是RefWatcher 的 watch 方法足够重要和复杂,所以有必要单独列一节仔细讲解内部原理。

    4.4.1 基础知识——弱引用 WeakReference 和 引用队列 ReferenceQueue

    关于引用类型和引用队列相关知识,读者可以参考白话 JVM——深入对象引用,这篇文章我认为讲解的比较清晰。

    这里,我简单举个例子,弱引用在定义的时候可以指定引用对象和一个 ReferenceQueue,弱引用对象在垃圾回收器执行回收方法时,如果原对象只有这个弱引用对象引用着,那么会回收原对象,并将弱引用对象加入到 ReferenceQueue,通过 ReferenceQueue 的 poll 方法,可以取出这个弱引用对象,获取弱引用对象本身的一些信息。看下面这个例子。

    mReferenceQueue = new ReferenceQueue<>();
    // 定义一个对象
    o = new Object();
    // 定义一个弱引用对象引用 o,并指定引用队列为 mReferenceQueue
    weakReference = new WeakReference<Object>(o, mReferenceQueue);
    // 去掉强引用
    o = null;
    // 触发应用进行垃圾回收
    Runtime.getRuntime().gc();
    // hack: 延时100ms,等待gc完成
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Reference ref = null;
    // 遍历 mReferenceQueue,取出所有弱引用
    while ((ref = mReferenceQueue.poll()) != null) {
        System.out.println("============ \n ref in queue");
    }
    复制代码
    

    打印结果为:

    ============ ref in queue

    4.4.2 基础知识——hprof文件

    hprof 文件可以展示某一时刻java堆的使用情况,根据这个文件我们可以分析出哪些对象占用大量内存和未在合适时机释放,从而定位内存泄漏问题。

    Android 生成 hprof 文件整体上有两种方式:

    1. 使用 adb 命令
    adb shell am dumpheap <processname> <FileName>
    复制代码
    
    1. 使用 android.os.Debug.dumpHprofData 方法 直接使用 Debug 类提供的 dumpHprofData 方法即可。
    Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
    复制代码
    

    Android Studio 自带 Android Profiler 的 Memory 模块的 dump 操作使用的是方法一。这两种方法生成的 .hprof 文件都是 Dalvik 格式,需要使用 AndroidSDK 提供的 hprof-conv 工具转换成J2SE HPROF格式才能在MAT等标准 hprof 工具中查看。

    hprof-conv dump.hprof converted-dump.hprof  
    复制代码
    

    至于hprof内部格式如何,本文不做具体介绍,以后有机会再单独写一篇文章来仔细讲解。LeakCanary 解析 .hprof 文件用的是 square 公司开源的另一项目:haha.

    4.4.3 watch方法

    终于到了 LeakCanary 关键部分了。我们从 watch 方法入手,前面的代码都是为了增强鲁棒性,我们直接从生成唯一id开始,LeakCanary 构造了一个带有 key 的弱引用对象,并且将 queue 设置为弱引用对象的引用队列。

    这里解释一下,为什么需要创建一个带有 key 的弱引用对象,不能直接使用 WeakReference 么? 举个例子,假设 OneActivity 发生了内存泄漏,那么执行 GC 操作时,肯定不会回收 Activity 对象,这样 WeakReference 对象也不会被回收。假设当前启动了 N 个 OneActivity,Dump内存时我们可以获取到内存中的所有 OneActivity,但是当我们准备去检测其中某一个 Activity 的泄漏问题时,我们就无法匹配。但是如果使用了带有 key 的 WeakReference 对象,发生泄露时泄漏时,key 的值也会 dump 保存下来,这样我们根据 key 的一一对应关系就能映射到某一个 Activity。

    然后,LeakCanary 调用了 ensureGoneAsync 方法去检测内存泄漏。

    public void watch(Object watchedReference, String referenceName) {
      if (this == DISABLED) {
        return;
      }
      checkNotNull(watchedReference, "watchedReference");
      checkNotNull(referenceName, "referenceName");
      final long watchStartNanoTime = System.nanoTime();
      // 对当前监视对象设置一个唯一 id
      String key = UUID.randomUUID().toString();
      // 添加到 Set<String> 中
      retainedKeys.add(key);
      // 构造一个带有id 的 WeakReference 对象
      final KeyedWeakReference reference =
          new KeyedWeakReference(watchedReference, key, referenceName, queue);
      // 检测对象是否被回收了
      ensureGoneAsync(watchStartNanoTime, reference);
    }
    复制代码
    

    4.4.4 ensureGoneAsync 方法

    ensureGoneAsync 方法构造了一个 Retryable 对象,并将它传给 watchExecutor 的 execute 方法。

    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
      // watchExecutor 是 AndroidWatchExecutor的一个实例
      watchExecutor.execute(new Retryable() {
        @Override public Retryable.Result run() {
          return ensureGone(reference, watchStartNanoTime);
        }
      });
    }
    复制代码
    

    watchExecutor 是 AndroidWatchExecutor 的一个实例, AndroidWatchExecutor 的 execute 方法的作用就是判断当前线程是否是主线程,如果是主线程,那么直接执行 waitForIdle 方法,否则通过 Handler 的 post 方法切换到主线程再执行 waitForIdle 方法。

    @Override public void execute(@NonNull Retryable retryable) {
      // 判断当前线程是否是主线程
      if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
        waitForIdle(retryable, 0);
      } else {
        postWaitForIdle(retryable, 0);
      }
    }
    复制代码
    

    waitForIdle 方法通过调用 addIdleHandler 方法,指定当主进程中没有需要处理的事件时,在这个空闲期间执行 postToBackgroundWithDelay 方法。

    private void waitForIdle(final Retryable retryable, final int failedAttempts) {
      // 由于上面的 execute 方法,已经保证了此方法在主线程中执行,所以Looper.myQueue()获取的主线程的消息队列
      Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override public boolean queueIdle() {
          postToBackgroundWithDelay(retryable, failedAttempts);
          // return false 表示执行完之后,就立即移除这个事件
          return false;
        }
      });
    }
    复制代码
    

    postToBackgroundWithDelay 方法首先会计算延迟时间 delayMillis,这个延时是有 exponentialBackoffFactor(指数因子) 乘以初始延时时间得到的, exponentialBackoffFactor(指数因子)会在2^n 和 Long.MAX_VALUE / initialDelayMillis 中取较小值,也就说延迟时间delayMillis = initialDelayMillis * 2^n,且不能超过 Long.MAX_VALUE。

    private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
       long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
       // 计算延迟时间
       long delayMillis = initialDelayMillis * exponentialBackoffFactor;
       // 切换到子线程中执行
       backgroundHandler.postDelayed(new Runnable() {
         @Override public void run() {
           // 执行 retryable 里的 run 方法
           Retryable.Result result = retryable.run();
           // 如果需要重试,那么再添加到主线程的空闲期间执行
           if (result == RETRY) {
             postWaitForIdle(retryable, failedAttempts + 1);
           }
         }
       }, delayMillis);
    }
    复制代码
    

    postToBackgroundWithDelay 方法每次执行会指数级增加延时时间,延时时间到了后,会执行 Retryable 里的方法,如果返回为重试,那么会增加延时时间并执行下一次。

    retryable.run() 的run 方法又执行了什么呢?别忘了我们ensureGoneAsync中的代码,一直在重试的代码正式 ensureGone 方法。

    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
      watchExecutor.execute(new Retryable() {
        @Override public Retryable.Result run() {
          return ensureGone(reference, watchStartNanoTime);
        }
      });
    }
    复制代码
    

    4.4.5 ensureGone 方法

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

    Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
      long gcStartNanoTime = System.nanoTime();
      // 前面不是有一个重试的机制么,这里会计下这次重试距离第一次执行花了多长时间
      long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
      // 移除所有弱引用可达对象,后面细讲
      removeWeaklyReachableReferences();
      // 判断当前是否正在开启USB调试,LeakCanary 的解释是调试时可能会触发不正确的内存泄漏
      if (debuggerControl.isDebuggerAttached()) {
        // The debugger can create false leaks.
        return RETRY;
      }
      // 上面执行 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, 代表这个对象已经被正常回收,不需要再被监视了。

    那么现在来看,判断这个对象是否被回收就比较简单了?

    private boolean gone(KeyedWeakReference reference) {
      // retainedKeys 中不包含 reference.key 的话,就代表这个对象已经被回收了
      return !retainedKeys.contains(reference.key);
    }
    复制代码
    

    (2). dumpHeap 方法

    heapDumper.dumpHeap() 是执行生成hprof的方法,heapDumper 是 AndroidHeapDumper 的一个对象,我们来具体看看它的 dump 方法。

    public File dumpHeap() {
      // 生成一个存储 hprof 的文件
      File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
      // 文件创建失败
      if (heapDumpFile == RETRY_LATER) {
        return RETRY_LATER;
      }
      // FutureResult 内部有一个 CountDownLatch,用于倒计时
      FutureResult<Toast> waitingForToast = new FutureResult<>();
      // 切换到主线程显示 toast
      showToast(waitingForToast);
      // 等待5秒,确保 toast 已完成显示
      if (!waitingForToast.wait(5, SECONDS)) {
        CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
        return RETRY_LATER;
      }
      // 创建一个通知
      Notification.Builder builder = new Notification.Builder(context)
          .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
      Notification notification = LeakCanaryInternals.buildNotification(context, builder);
      NotificationManager notificationManager =
          (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
      int notificationId = (int) SystemClock.uptimeMillis();
      notificationManager.notify(notificationId, notification);
    
      Toast toast = waitingForToast.get();
      try {
        // 开始 dump 内存到指定文件
        Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
        cancelToast(toast);
        notificationManager.cancel(notificationId);
        return heapDumpFile;
      } catch (Exception e) {
        CanaryLog.d(e, "Could not dump heap");
        // Abort heap dump
        return RETRY_LATER;
      }
    }
    复制代码
    

    这段代码里我们需要看看 showToast() 方法,以及它是如何确保 toast 已完成显示(有点黑科技的感觉)。

    private void showToast(final FutureResult<Toast> waitingForToast) {
      mainHandler.post(new Runnable() {
        @Override public void run() {
          // 当前 Activity 已经 paused的话,直接返回
          if (resumedActivity == null) {
            waitingForToast.set(null);
            return;
          }
          // 构建一个toast 对象
          final Toast toast = new Toast(resumedActivity);
          toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
          toast.setDuration(Toast.LENGTH_LONG);
          LayoutInflater inflater = LayoutInflater.from(resumedActivity);
          toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
          // 将toast加入显示队列
          toast.show();
          // Waiting for Idle to make sure Toast gets rendered.
          // 主线程中添加空闲时操作,如果主线程是空闲的,会将CountDownLatch执行 countDown 操作
          Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override public boolean queueIdle() {
              waitingForToast.set(toast);
              return false;
            }
          });
        }
      });
    }
    复制代码
    

    首先我们需要知道所有的Toast对象,并不是我们一调用 show 方法就立即显示的。NotificationServiceManager会从mToastQueue中轮询去除Toast对象进行显示。如果Toast的显示不是实时的,那么我们如何知道Toast是否已经显示完成了呢?我们在 Toast 调用 show 方法后调用 addIdleHandler, 在主进程空闲时执行 CountDownLatch 的减一操作。由于我们知道我们顺序加入到主线程的消息队列中的操作:先显示Toast,再执行 CountDownLatch 减一操作。所以在 if (!waitingForToast.wait(5, SECONDS)) 的判断中,我们最多等待5秒,如果超时会走重试机制,如果我们的 CountDownLatch 已经执行了减一操作,则会正常走后续流程,同时我们也能推理出它前面 toast 肯定已经显示完成了。

    Debug.dumpHprofData(heapDumpFile.getAbsolutePath());是系统Debug类提供的方法,我就不做具体分析了。

    (3). heapdumpListener.analyze(heapDump) 方法

    heapdumpListener 是 ServiceHeapDumpListener 的一个对象,最终执行了HeapAnalyzerService.runAnalysis方法。

    @Override public void analyze(@NonNull HeapDump heapDump) {
      checkNotNull(heapDump, "heapDump");
      HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
    }
    复制代码
    
    // 启动前台服务
    public static void runAnalysis(Context context, HeapDump heapDump,
          Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
      setEnabledBlocking(context, HeapAnalyzerService.class, true);
      setEnabledBlocking(context, listenerServiceClass, true);
      Intent intent = new Intent(context, HeapAnalyzerService.class);
      intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
      intent.putExtra(HEAPDUMP_EXTRA, heapDump);
      ContextCompat.startForegroundService(context, intent);
    }
    复制代码
    

    HeapAnalyzerService 继承自 IntentService,IntentService的具体原理我就不多做解释了。IntentService会将所有并发的启动服务操作,变成顺序执行 onHandleIntent 方法。

    @Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
      if (intent == null) {
        CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
        return;
      }
      // 监听 hprof 文件分析结果的类
      String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
      // hprof 文件类
      HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
    
      HeapAnalyzer heapAnalyzer =
          new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
      // checkForLeak 会调用 haha 组件中的工具,分析 hprof 文件
      AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
          heapDump.computeRetainedHeapSize);
      // 将分析结果发送给监听器 listenerClassName
      AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
    }
    复制代码
    

    我们来看下 checkForLeak 方法,我们一起来看下吧。

    public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
          @NonNull String referenceKey,
          boolean computeRetainedSize) {
      long analysisStartNanoTime = System.nanoTime();
      // 文件不存在的话,直接返回
      if (!heapDumpFile.exists()) {
        Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
        return failure(exception, since(analysisStartNanoTime));
      }
    
      try {
        // 更新进度回调
        listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
        // 将 hprof 文件解析成 Snapshot
        HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
        HprofParser parser = new HprofParser(buffer);
        listener.onProgressUpdate(PARSING_HEAP_DUMP);
        Snapshot snapshot = parser.parse();
        listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
        // 移除相同 GC root项
        deduplicateGcRoots(snapshot);
        listener.onProgressUpdate(FINDING_LEAKING_REF);
        // 查找内存泄漏项
        Instance leakingRef = findLeakingReference(referenceKey, snapshot);
    
        // False alarm, weak reference was cleared in between key check and heap dump.
        // 没有找到,就代表没有泄漏
        if (leakingRef == null) {
          return noLeak(since(analysisStartNanoTime));
        }
        // 找到泄漏处的引用关系链
        return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
      } catch (Throwable e) {
        return failure(e, since(analysisStartNanoTime));
      }
    }
    复制代码
    

    hprof 文件的解析是由开源项目 haha 完成的,我这里不做过多展开。

    findLeakingReference 方法是查找泄漏的引用处,我们看下代码:

    private Instance findLeakingReference(String key, Snapshot snapshot) {
      // 从 hprof 文件保存的对象中找到所有 KeyedWeakReference 的实例
      ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
      if (refClass == null) {
        throw new IllegalStateException(
            "Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");
      }
      List<String> keysFound = new ArrayList<>();
      // 对 KeyedWeakReference 实例列表进行遍历
      for (Instance instance : refClass.getInstancesList()) {
        // 获取每个实例里的所有字段
        List<ClassInstance.FieldValue> values = classInstanceValues(instance);
        // 找到 key 字段对应的值
        Object keyFieldValue = fieldValue(values, "key");
        if (keyFieldValue == null) {
          keysFound.add(null);
          continue;
        }
        // 将 keyFieldValue 转为 String 对象
        String keyCandidate = asString(keyFieldValue);
        // 如果这个对象的 key 和 我们查找的 key 相同,那么返回这个弱对象持有的原对象
        if (keyCandidate.equals(key)) {
          return fieldValue(values, "referent");
        }
        keysFound.add(keyCandidate);
      }
      throw new IllegalStateException(
          "Could not find weak reference with key " + key + " in " + keysFound);
    }
    复制代码
    

    到现在为止,我们已经把 LeakCanary 检测内存泄漏的全部过程的源码看完了。个人认为 LeakCanary 源码写的不错,可读性很高,查找调用关系也比较方便(这里黑一下 bilibili 的 DanmakusFlameMaster)。

    作者:orzangleli
    链接:https://juejin.im/post/5c054e91e51d45242906ed68
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

        本文标题:LeakCanary 内存泄漏原理完全解析

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