LeakCanary原理解析

前几天面试,问到了我内存泄漏的相关问题,顺其自然问到了内存泄漏的检测工具LeakCanary的工作原理。当时不会,在看了几篇博文后,明白了其中的道理,理一下思路记录在此。

准备知识

ReferenceQueue

    引用队列,对于弱引用和软引用来说,若需要知道该引用是否已被GC回收,那么在构造WeakReference或SoftReference时,传入一个ReferenceQueue。那么当这个引用被回收后,就可以通过ReferenceQueue.poll()或remove()拿到该引用,做额外处理,例如清理数据等。下面看一个例子,也是网上唯一关于ReferenceQueue的例子,很好理解,直接拿过来用。

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

检测泄漏步骤

    一般情况下,检测activity是否泄漏,首先第一步,获得activity被销毁的信息,也就是要监听到activity的生命周期方法。接下来,既然这个activity已经被destory了,那么它理应被GC回收,也就是第二步,判断该对象(activity)是否被回收。若回收了那就是没泄漏,若判断该对象没被回收,说明该对象泄漏了,进行第三步,展示泄漏信息。总结下:

1、监听activity被销毁的消息

2、判断该activity是否被销毁

3、根据步骤2结果展示泄漏信息

从LeakCanary源码分析原理

对于应用来说,在Application.onCreate()中调用一句LeakCanary.install(this)即可,也就是说对于LeakCanary来说,传入一个application对象即可启动检测内存泄漏功能。那我们从该行代码向内部探究:

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

有一定经验的老程可以看出,这里是标准的建造者模式,每一个方法返回的都是Builder保证以流式调用。那我们看看构造一个RefWatcher需要建造哪些组件

AndroidRefWatchBuilder.listenerServiceClass()

  public AndroidRefWatcherBuilder listenerServiceClass(
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

往下走进入AndroidRefWatchBuilder父类方法RefWatcherBuilder.heapDumpListener():

  /** @see HeapDump.Listener */
  public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
    this.heapDumpListener = heapDumpListener;
    return self();
  }

可以看到,这里给成员变量heapDumpListener赋值,也就是说,接下来build Watcher时,需要builder提供heapDumpListener。(不熟悉的同学可以先看看建造者模式)这个变量的作用我们暂时不做研究,后续讲。

第二个方法exludedRefs,同样也是给Builder的成员变量赋值:

  /** @see ExcludedRefs */
  public final T excludedRefs(ExcludedRefs excludedRefs) {
    this.excludedRefs = excludedRefs;
    return self();
  }

重点是设置好成员变量后,调用buildAndInstall()方法,如下:

 public RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    //调用build方法构造
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      if (watchActivities) {
        //传入build好的RefWatch,调用静态方法进行install
        ActivityRefWatcher.install((Application) context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

build()方法没什么好讲的,完全是build设计模式,构造出一个RefWatcher,我们进入install方法:

  public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }
构造ActivityRefWatcher并调用watchActivities()监控activity,其实我们可以从ActivityRefWatcher这个类的名字看出,它是负责监控Activity的,那么,我们想想作为第三方应用来说,如何监控应用内的每一个Activity?可以通过系统提供给我们的接口ActivityLifecycleCallbacks,该接口的回调方法与activity生命周期回调方法对应,每当有activity创建或销毁时,可以通过回调方法通知外部,watchActivities方法每部就是采用的这个接口:
  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

我们看下变量lifecycleCallbacks:

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

可以看到,这里只监控了activityDestory,当有activity被销毁时,交给onActivityDestoryed()处理,onActivityDestory方法:

  void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }

交给watcher处理。到此,我们完成了第一步:监听activity销毁信息

接下来,研究下watch()方法是如何判断activity是否泄漏的,这里便用到了预备知识中所讲的ReferenceQueue。watch方法:

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

注意,这里将activity、唯一生成的key、还有queue(是一个ReferenceQueue,在watcher构造时new出来的)构造一个KeyedWeakReference。并将key值存入retainKeys中保管。该类是WeakReference的子类,如下:

final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;
  public final String name;

  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}

这里,我们将activity包装弱引用,并添加了referenceQueue,那么当该activity被GC回收时,我们就可以从referenceQueue中获取该activity的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);
    }
  }

gone()方法:

 private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

这一部分,注释写的比较清楚,就不再赘述,可以对照代码理解。当该activity被判断为泄漏,就要进行第三步:展示泄漏信息,也就是获取dumpheap以及对这个dumpheap进行analyze。调用比较简单,而逻辑实现又是依托另外一个项目:HAHA。调用如下:

 //若retainedKeys中还存在该reference(还没有被滤掉),则判断为该reference泄漏,进行下一步dump内存快照
    //展示泄漏信息
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      //获取dump文件
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      //调用heapdumpListener.analyze进行分析
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }

这里的heapdumpListener就是第一步构造watcher时,builder中创建的heapdumpListener。追下去可以看到ServiceHeapDumpListener这个类封装了analyze逻辑:

  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }

追进去runAnalysis()方法中就是启动一个服务处理heapDump:

  public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

在HeapAnalyzerService.onHandIntent()中收到启动服务请求,调用checkForLeak:

@Override protected void onHandleIntent(Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

关键方法checkForLeak():

 public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      //将heapDumpFile转化为Snapshot
      Snapshot snapshot = parser.parse();
      deduplicateGcRoots(snapshot);

      //找到泄漏对象引用
      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);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }

上面的checkForLeak方法就是输入.hprof,输出分析结果,主要有以下几个步骤:

1.把.hprof转为Snapshot,这个Snapshot对象就包含了对象引用的所有路径

2.精简gcroots,把重复的路径删除,重新封装成不重复的路径的容器

3.找出泄漏的对象

4.找出泄漏对象的最短路径

这里重点分析一下第三步:

private Instance findLeakingReference(String key, Snapshot snapshot) {
    //找到快照中的KeyedWeakReference类对象
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    List<String> keysFound = new ArrayList<>();
    //遍历这个类的所有实例
    for (Instance instance : refClass.getInstancesList()) {
      List<ClassInstance.FieldValue> values = classInstanceValues(instance);
      //key值和最开始定义封装的key值相同,说明该实例是泄漏对象
      String keyCandidate = asString(fieldValue(values, "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中学习到很多内容,在编码时借鉴,例如建造者模式、解耦、过滤等。

猜你喜欢

转载自blog.csdn.net/u012545728/article/details/80567136