内存优化之三——内存泄漏监控工具原理分析


用简单通俗的话来记录自己对架构的理解

1.背景

对于DAU上亿的应用来讲,是无法在开发测试阶段,完全覆盖所有场景的。所以,线上的的性能监控就显得尤为重要。在大司,一般都有自己的APM(Application Performance Monitor),监控启动速度,崩溃、OOM、内存、卡顿等。对于内存,主要是内存泄漏和内存溢出等。针对APM,腾讯推出了QAPM内测版,其它大厂也都有自己的XAPM,对于其它公司,更多使用的是在开发测试阶段的LeakCanary,不过,原理是一样的,今天就以LeakCanary进行内存泄漏监控的分析。

2.原理解析

把原理图解白活明白,咱们再进入源码的海洋中。

(1).检测内存泄漏模块

(a)利用Application的Activity的onDestroy的回调
Application.registerActivityLifecycleCallback(new ActivityLifecycleCallback(0{})
(b)onDestory回调时
I 创建WeakReference,将ReferenceQueue和回收页面传入。
II 定期(5s)检查RefereceQueue是否有弱引用对象,
III RefereceQueue不存在WeakReferece引用对象,则手动触发GC(System.gc),再次检查RefereceQueue队列中是否存在WeakReferece引用对象。存在,则证明WeakReference引用的对象页面不存在内存泄漏,不存在,则证明存在内存泄漏。
对于上面的描述,实际上大家不禁有以下两个问题:
(1)WeakReference与页面存在什么关系?
在onDestroy监听回调时,将Activity/Fragment和ReferenceQueue对象传入。WeakReference会引用Activity/Fragment。
(2)ReferenceQueue与WeakReference存在什么关系?
GC时,执行了onDestroy且无可达性的页面弱引用会添加到ReferenceQueue。等待内存的回收。比如MainActivity执行了onDestroy,且没有强引用,则对应的WeakReference则会被添加到ReferenceQueue.

(2).dump Heap内存快照模块

利用Debug.dumpHprofData(String fileName)保存内存快照

(3).内存分析模块

耗时操作,独立进程:
在LeakCanary 2.0之前使用haha 进行分析
在LeakCanary 2.0后,使用XX分析

(4).分析通知推送/服务器上报模块

LeakCanary启动服务进行推送通知

3.源码解析

1.版本依赖

基于Leakcanary 1.6.0版本
(1)gradle依赖

   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'

(2)Application处理

 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);

2.源码解析

1.创建RefWatcher

  /**
   * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
   * references (on ICS+).
   */
  public static RefWatcher install(Application application) {
    return refWatcher(application) //建造者模式Builder
    .listenerServiceClass(DisplayLeakService.class)//分析服务结果监听
    .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())//排除系统内部应用开发者无法处理存泄漏索引
    .buildAndInstall();//创建RefWatcher
  }

2.buildAndInstall的分析

public final RefWatcher build() {
	//
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }
	//构建HeapDump建造者模式,对dump文件进行管理
    if (heapDumpBuilder.excludedRefs == null) {
      heapDumpBuilder.excludedRefs(defaultExcludedRefs());
    }
	//HeapDump文件的监听
    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }
	//指向页面的弱引用是否是被添加到了队列,从而决定是否再次GC
    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }

    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }

    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      watchExecutor = defaultWatchExecutor();
    }
	//GC的处理,使用的是Runtime.getRuntime().gc
    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }

    if (heapDumpBuilder.reachabilityInspectorClasses == null) {
      heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
    }

    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        heapDumpBuilder);
  }

3.RefWatcher的分析

RefWatcher是个核心类,尤其是watch方法,现在,让我们看下它的真面目

 /**
   * Watches the provided references and checks if it can be GCed. This method is non blocking,
   * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
   * with.
   *
   * @param referenceName An logical identifier for the watched object.
   */
  public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
	//弱引用指向的对象确认是否被回收
    ensureGoneAsync(watchStartNanoTime, reference);
  }

ensureGoneAsync的解析

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  //监听执行
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }
 Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    //是否进行了回收判断
    if (gone(reference)) {
      return DONE;
    }
    //未被回收,触发GC
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    //检查弱引用是否在队列中
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
	//进行内存泄漏的dump文件获取
      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();
	//解析内存泄漏的文件
      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }

4.问题答疑

1.LeakCanary监听不实时?

下载和解析dump文件是个异步耗时工作,只要是有权限,会在通知栏中进行dump文件的下载和分析实时展示。

2.LeakCanary是major gc还是full gc?

LeakCanary使用的是Runtime.getRuntime().gc(),它是针对老年代和年青代的full gc!

3.Fragment如何实现的监听回调。

有同学说,在如下代码中看到了Activity.onDestroy是在Application进行注册监听,但是,Fragment在Application中并没有,那么,它是如何注册的呢?让我们来look!

public static void install(Context context, RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

//利用Application的注册   application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

//FragmentManager的注册

  @Override public void watchFragments(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
  }

5.站在巨人的肩膀

1.腾讯QAPM的内测申请
https://cloud.tencent.com/product/qapm/getting-started
2.腾讯QAPM的原理
https://cloud.tencent.com/developer/article/1385928

3.源码解析参考
https://blog.csdn.net/braintt/article/details/99685243
https://www.jianshu.com/p/261e70f3083f

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值