内存泄漏检测工具LeakCanary

内存检测工具一直在用,但是它的原理却只是一知半解,看了遍源码同时站在巨人的肩膀捋了一遍思路。发现前人分析的源码与当前新码有所不同,但是精髓原理是一致的,所以总结记录下。

1、使用方法简单,引入依赖,在Application中install就OK;

2、内部处理其实是在Application中regist了一个Activity生命周期的回调,监听了Activity在onDestroy状态;

3、通过一系列引用存储、手动GC立即执行(调用系统GC不确定什么时候执行)、判断引用是否存在来确定Activity是否内存泄漏;

4、发出通知,将泄漏点显示在UI上。

具体看代码:

public static RefWatcher install(Application application) {
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}

以前的版本是在install方法中实例化RefWatcher,现在是用build模式在最后buildAndInstall方法中处理。

public RefWatcher buildAndInstall() {
  RefWatcher refWatcher = build();
  if (refWatcher != DISABLED) {
    LeakCanary.enableDisplayLeakActivity(context);
    ActivityRefWatcher.install((Application) context, refWatcher);
  }
  return refWatcher;
}

在build方法中返回一个RefWatcher,

public final RefWatcher build() {
  if (isDisabled()) {
    return RefWatcher.DISABLED;
  }

  ExcludedRefs excludedRefs = this.excludedRefs;
  if (excludedRefs == null) {
    excludedRefs = defaultExcludedRefs();
  }

  HeapDump.Listener heapDumpListener = this.heapDumpListener;
  if (heapDumpListener == null) {
    heapDumpListener = defaultHeapDumpListener();
  }

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

  GcTrigger gcTrigger = this.gcTrigger;
  if (gcTrigger == null) {
    gcTrigger = defaultGcTrigger();
  }

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

插一句:RefWatcher到底是什么东西?其实他是一个监视器,监视Reference引用的。

在install中,用到一个ActivityRefWatcher类,这个类有点关键,

public static void install(Application application, RefWatcher refWatcher) {
  new ActivityRefWatcher(application, refWatcher).watchActivities();
}

重点看watchActivities方法,通过名称就知道是干嘛的,

public void watchActivities() {
  // Make sure you don't get installed twice.
  stopWatchingActivities();
  application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

首先停止监听所有Activity,确保不会install两次,然后就调用了Application的注册监听方法,将生命周期回调传入Application中,如果生命周期发生变化,将会回调

public interface ActivityLifecycleCallbacks {
    void onActivityCreated(Activity activity, Bundle savedInstanceState);
    void onActivityStarted(Activity activity);
    void onActivityResumed(Activity activity);
    void onActivityPaused(Activity activity);
    void onActivityStopped(Activity activity);
    void onActivitySaveInstanceState(Activity activity, Bundle outState);
    void onActivityDestroyed(Activity activity);
}

但是ActivityRefWatcher却只监听了onDestroy的回调

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
    new Application.ActivityLifecycleCallbacks() {
      @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
      }

      @Override public void onActivityStarted(Activity activity) {
      }

      @Override public void onActivityResumed(Activity activity) {
      }

      @Override public void onActivityPaused(Activity activity) {
      }

      @Override public void onActivityStopped(Activity activity) {
      }

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

      @Override public void onActivityDestroyed(Activity activity) {
        ActivityRefWatcher.this.onActivityDestroyed(activity);
      }
    };
void onActivityDestroyed(Activity activity) {
  refWatcher.watch(activity);
}

然后这个refWatcher就是Application中install时,生成的监视工具,要对这个生命周期到达onDestroy的Activity进行处理。再看watch方法

public void watch(Object watchedReference) {
  watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
  ......
  ensureGoneAsync(watchStartNanoTime, reference);
}
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}

这里watch的是一个Object类型哦,可不可以说LeakCanary可以监听所有Java中对象的内存泄漏呢(猜的)。


当Activity生命周期走到了onDestroy时,RefWatcher收到回调后什么时候去执行这个泄漏检测呢?

经过耐心寻找,终于找到了蛛丝马迹:

再AndroidRefWatcherBuilder中有个常量

private static final long DEFAULT_WATCH_DELAY_MILLIS = SECONDS.toMillis(5);

5秒的时间,看下它在哪里用到

@Override protected WatchExecutor defaultWatchExecutor() {
  return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}

new实例的时候传入的默认时间,AndroidWatchExecutor继承于WatchExecutor,构造传参接收了这个5秒默认时间,同时还创建了一个工作线程的Handler

public AndroidWatchExecutor(long initialDelayMillis) {
  mainHandler = new Handler(Looper.getMainLooper());
  HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
  handlerThread.start();
  backgroundHandler = new Handler(handlerThread.getLooper());
  this.initialDelayMillis = initialDelayMillis;
  maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}

在上面watch里会调用到extcutor.execute方法,看下有哪些操作

@Override public void execute(Retryable retryable) {
  if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
    waitForIdle(retryable, 0);
  } else {
    postWaitForIdle(retryable, 0);
  }
}

先判断当前线程是否为主线程,如果是就waitForIdle,这是啥,是一个主线程中的操作,但是不会阻塞线程,会在主线程处理完msg后空闲的回调,然后可以做耗时操作,renturn false只执行一次,return true一直执行!

void waitForIdle(final Retryable retryable, final int failedAttempts) {
  // This needs to be called from the main thread.
  Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override public boolean queueIdle() {
      postToBackgroundWithDelay(retryable, failedAttempts);
      return false;
    }
  });
}
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.Result result = retryable.run();
      if (result == RETRY) {
        postWaitForIdle(retryable, failedAttempts + 1);
      }
    }
  }, delayMillis);
}

initialDelayMillis就是延时post的5秒钟,backgroundHandler就是工作线程handler,所以,监听到Activity走到onDestroy时先收到主线程空闲的回调,再执行一个5秒延迟的postDelay,才会去检测这个Activity是否内存泄漏。


回正题,RefWatcher如何检测Activity是否内存泄漏呢?

具体分析看下前人文章具体过程


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;
  }
  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);
    heapdumpListener.analyze(
        new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
            gcDurationMs, heapDumpDurationMs));
  }
  return DONE;
}

处理过程中有个GC关键点,手动立即执行

@Override public void runGc() {
  // Code taken from AOSP FinalizationTest:
  // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
  // java/lang/ref/FinalizationTester.java
  // System.gc() does not garbage collect every time. Runtime.gc() is
  // more likely to perfom a gc.
  Runtime.getRuntime().gc();
  enqueueReferences();
  System.runFinalization();
}

调用Runtime.getRuntime().gc(),可以立即执行。

 

 

两个彩蛋:一个是知道了主线程监听线程空闲的回调;

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {}

一个是系统GC可以手动调用立即执行。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值