leakcanary探索

leakcanary探索


Android应用开发中,OutOfMemory是一个很让人头疼的问题,特别是迭代时间较长的项目中,问题定位较难。在gitHub上发现一个开源项目——leakcanary,可以辅助检测并且定位内存泄漏。抽个时间研究一下它的实现。

什么是leakcanary

A memory leak detection library for Android and Java.

leakcanary 包装了简单易用的接口,只需简单若干行代码,就可以开始工作。通过监控对象的回收情况,定位内存泄漏并自动执行HeapDump,自动分析hrpof文件找出泄露的引用链,在通知栏自动提示用户。
项目内置默认实现了ActivityRefWatcher,用于监控Activity,但只能用于Android 4.0及其之上。如果想支持4.0之下的系统,需要定义BaseActivity复写onDestroy来获得统一监控触发点。
理论上,leakcanary可以做到对象级别的内存泄露分析,同样的需要自己实现对应的RefWatcher,在合适的时机进行触发分析。

粗略工作流程

Created with Raphaël 2.1.2 Start:触发监控 ref信息记录 对象引用是否泄漏 内存分析 headDump分析 通知展示分析结果 End yes no

根据上面的流程图,从技术实现上,个人比较关注的是两个点:

  • 如何判断一个对象属于内存泄漏
  • 如何在hprof文件里面定位到问题并抓取出来

内存泄露判定实现

内存泄漏,指的是脱离了使用场景的对象,系统进行回收的时候无法成功释放资源,导致系列的问题。根据定义,思路大概是这样:

Created with Raphaël 2.1.2 Start 强制系统回收 对象引用是否可达 内存分析 End yes no
系统回收
GcTrigger DEFAULT = new GcTrigger() {
    @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();    //不确保一定能够触发gc
      enqueueReferences();  //sleep一定时间(100ms),来确保gc完成
      System.runFinalization();//为什么要放到enqueueReferences()后面?
    }

如我们所知,Java里面的gc拥有各种各样的垃圾收集算法,gc的执行具有很大的不确定性。不管是用System.gc()还是Runtime.getRuntime().gc(),都是对jvm的一个建议,引发jvm的内部垃圾算法的加权,无法保证gc一定马上执行。所以即使sleep了一定的时间等待gc,仍有极大的可能误报

对象引用可达性判断

先让我们来看一下逻辑的执行顺序。

  1. 对象引用生成key并记录

    public void watch(Object watchedReference, String referenceName) {
        ...检查...
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);//直接记录key
        //使用ReferenceQueue和WeakReference来记录引用被回收
        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
        ...异步执行gc...
    }

    注意到这里使用了ReferenceQueue,查阅文档得知,弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
    不同类型Reference的解释

  2. 实际的gc和判断过程

    void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
    
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
        removeWeaklyReachableReferences();
        if (gone(reference) || debuggerControl.isDebuggerAttached()) {
          return;
        }
        gcTrigger.runGc();//实际执行gc
        removeWeaklyReachableReferences();
        //如果引用对应的key仍然存在,认为是泄漏,执行heapDump
        if (!gone(reference)) {
          long startDumpHeap = System.nanoTime();
          long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
          File heapDumpFile = heapDumper.dumpHeap();
          if (heapDumpFile == null) {
            // Could not dump the heap, abort.
            return;
          }
          long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
          heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,heapDumpDurationMs));
        }
    }
    
    //简单的key判断
    private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
    }
    
    //凡是存在ReferenceQueue中的,都认为是被回收的,remove之
    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;
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
            retainedKeys.remove(ref.key);
        }
    }

    回顾这个过程,有一个比较明显的问题,就是watch的时候是直接执行了retainedKeys.add(key),意图在后期通过GC来利用ReferenceQueue的enqueue,从而remove(key)。而在前面的分析中,GC的不确定是很强的,所以存在误报的可能。

heapDump的分析

待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值