LeakCanary原理分析

很久没有写博客了,最近年关将近,也有时间去回顾和温习一下之前的技术笔记,突然发现了之写的一篇,温故了一遍,感觉没有描述清楚,于是去网上又查了一下,千篇一律的都是这么几个步骤:

1.通过Application.registerActivityLifecycleCallbacks()方法注册Activity生命周期,在onDestroy的时候进行watch

2.将Activity通过弱引用KeyedWeakReference,并生成一个序列号对应这个弱引用,封装到一个ReferenceQueue中

3.首先通过removeWeaklyReachablereference来移除已经被回收的Activity引用

4.通过gone(reference)判断当前弱引用对应的Activity是否已经被回收,如果已经回收说明activity能够被GC,直接返回即可

5.如果Activity没有被回收,调用GcTigger.runGc方法运行GC,GC完成后在运行第1步,然后运行第2步判断Activity是否被回收了,如果这时候还没有被回收,那就说明Activity可能已经泄露

6.如果Activity泄露了,就抓取内存dump文件(Debug.dumpHprofData)

7.之后通过HeapAnalyzerService.runAnalysis进行分析内存文件分析

8.最后通过DisplayLeakService进行内存泄漏的展示

实际上,并没有说到根子上。

其实LeakCanary检测内存泄露是用了ReferenceQueue,下面重点说一下ReferenceQueue

ReferenceQueue
引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到队列中,ReferenceQueue实现了入队(enqueue)和出队(poll),还有remove操作,内部元素head就是泛型的Reference。
简单例子
当我们想检测一个对象是否被回收了,那么我们就可以采用 Reference + ReferenceQueue,大概需要几个步骤:

  • 创建一个引用队列 queue
  • 创建 Refrence 对象,并关联引用队列 queue
  • 在 reference 被回收的时候,refrence 会被添加到 queue 中
public class RefTest {
    private static ReferenceQueue<byte[]> rq = new ReferenceQueue<byte[]>();
    private static int _1M = 1024*1024;
 
    public static void main(String[] args) {
        Object value = new Object();
        Map<Object, Object> map = new HashMap<>();
        Thread thread = new Thread(() -> {
            try {
                int cnt = 0;
                WeakReference<byte[]> k;
                while((k = (WeakReference) rq.remove()) != null) {
                    System.out.println((cnt++) + "回收了:" + k);
                }
            } catch(InterruptedException e) {
                //结束循环
            }
        });
        thread.setDaemon(true);
        thread.start();
 
        for(int i = 0;i < 10000;i++) {
            byte[] bytes = new byte[_1M];
           //注意构造弱引用时传入rq           
            WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes, rq);
            map.put(weakReference, value);
        }
        System.out.println("map.size->" + map.size());
    }
}

结果

9992回收了:java.lang.ref.WeakReference@1d13cd4
9993回收了:java.lang.ref.WeakReference@118b73a
9994回收了:java.lang.ref.WeakReference@1865933
9995回收了:java.lang.ref.WeakReference@ad82c
map.size->10000

看完ReferenceQueue再来回顾一下LeakCanary的实现:
先来看下watch()方法是如何判断activity是否泄漏的

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

接下来直接看核心方法ensureGone

 Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
 
    //将已被回收的activity对象的keyedWeakReference的key值从retainedKeys中删除,以达到
    //过滤目的
    removeWeaklyReachableReferences();
 
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    //如果retainedKeys中不存在reference,说明它已经被回收,返回
    if (gone(reference)) {
      return DONE;
    }
    //手动调用GC
    gcTrigger.runGc();
    //再次过滤
    removeWeaklyReachableReferences();
    //若retainedKeys中还存在该reference(还没有被滤掉),则判断为该reference泄漏,进行下一步dump内存快照
    //展示泄漏信息
    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;
  }

看下removeWeakReachableReferences()方法:

private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    //若queue中存在该keyedWeakReference,则说明该keyedWeakReference对应的activity已被回收
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      //从retainedKeys中移除,则retainedKeys剩下的就是泄漏的
      retainedKeys.remove(ref.key);
    }
  }

若queue中存在该keyedWeakReference,则说明该keyedWeakReference对应的activity已被回收,反之则activity可能发生泄露。

至此,LeakCanary的原理基本上清楚了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值