Bootstrap

LeakCanray源码分析

一、背景

LeakCanray 是由square开源的一款轻量的第三方开源内存检测工具,让我们可以方便复查排查问题的所在。

二、Java内存分析

2.1 栈内存:存放我们的基本类型

2.2 堆内存:存放我们new 出来的对象由java虚拟机来管理

2.3 方法区:存放方法和静态变量

2.4 内存泄露的原因,一个长生命周期对象持有另外一个对象的引用导致对象的无法回收所以泄露,会导致OOM的主要原因之一

三、常见内存泄露案例

3.1单例导致的内存泄露 因为传入context的话会被单例一直引用,但是单例生命周期是永久所以把传入context转成context.getApplicationContext()

3.2 Handler 造成内存泄露,因为我们再处理完逻辑之后,我们可能用handler做处理,如果销毁的时候message没有被处理完成,那么就会被主线程持有,导致内存泄露,解决办法在Activity销毁的时候将消息队列给清空掉。

3.3 线程导致的内存泄露,因为Activity在销毁的时候 线程还没有执行完毕 解决方法是将线程定义成静态内部类

3.4 webView 加载页面导致的内存泄露,解决办法就是在销毁的时候将webView所处的Activity放在一个单独的进程中然后再杀死这个进程

四、LeakCanray原理

4.1Activity Destroy后 将它放在一个WeakReference

4.2 这个WeakReference关联到一个ReferenceQueue

4.3 查看ReferenceQueue是否存在Activity的引用

4.4 如果泄露了Dump出heap信息,然后分析路路径

五、引用类型

5.1 强引用:就是正常引用 系统即使OOM也不会回收

5.2 软引用:内存空间不够就会回收内存

5.3 弱引用:弱引用内存扫描到就会回收

5.4 虚引用: 形同虚设

5.5 当对象被回收的时候java虚拟机会把引用放到与之相关的引用队列中

六、源码解析

6.1

最简单的使用

        LeakCanary.install(this);

我们的点进去install实现

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

其实就是返回RefWatcher 对象

 public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();//创建多个对象
    if (refWatcher != DISABLED) {
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);//是否要展示内存泄露
      }
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher); //监听Activity·生命周期
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);//监听Fragment的生命周期
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

我们需要看到ActivityRefWatcher的install方法 这个方法就是注册一个生命周期的回调在Activity销毁的时候调用refWatcher的watch方法

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

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };

 private final WatchExecutor watchExecutor;
  private final DebuggerControl debuggerControl;//调试控制
  private final GcTrigger gcTrigger;//处理GC的
  private final HeapDumper heapDumper;//栈堆
  private final HeapDump.Listener heapdumpListener;//
  private final HeapDump.Builder heapDumpBuilder;
  private final Set<String> retainedKeys;//有内存泄露的key
  private final ReferenceQueue<Object> queue;//引用队列

好了 我们看到watch方法

  public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }

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();//先获取对象唯一的key值
    retainedKeys.add(key);//添加到集合之中
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);//创建一个弱引用

    ensureGoneAsync(watchStartNanoTime, reference);//确认这个弱引用是否被回收了
  }
 private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {//在线程池中去执行ensureGone方法
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

其实最后是到了 ensureGone 这个核心方法中

 Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    removeWeaklyReachableReferences();
    if (debuggerControl.isDebuggerAttached()) {//如果是debug状态那么久不会内存管理
      // The debugger can create false leaks.
      return RETRY;
    }
    if (gone(reference)) {//如果可达 那么不属于内存泄露
      return DONE;
    }
    gcTrigger.runGc();//先手动gc一波
    removeWeaklyReachableReferences(); //
    if (!go
ne(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);

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();//生产出一个heapDump 对象

      heapdumpListener.analyze(heapDump); 分析原因
    }
    return DONE;
  }

我们看一下analyze这个方法

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

我们看下runAnalysis 这个方法


   Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    setEnabledBlocking(context, HeapAnalyzerService.class, true);
    setEnabledBlocking(context, listenerServiceClass, true);
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    ContextCompat.startForegroundService(context, intent);

因为runAnalysis是属于IntentService 所以每次调用 ContextCompat.startForegroundService(context, intent);会回调onHandleIntentInForeground这个方法

  @Override protected void onHandleIntentInForeground(@Nullable 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, this, heapDump.reachabilityInspectorClasses);

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

我们看一下其中最重要的checkForLeak方法

它的作用是先转成内存快照snapshot,优化gcroots
找出泄露的对象找出泄露对象的最短路径
  public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
      @NonNull String referenceKey,
      boolean computeRetainedSize) {
    long analysisStartNanoTime = System.nanoTime();

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

    try {
      listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      listener.onProgressUpdate(PARSING_HEAP_DUMP);
      Snapshot snapshot = parser.parse();
      listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
      deduplicateGcRoots(snapshot);
      listener.onProgressUpdate(FINDING_LEAKING_REF);
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);//找到是否内存泄露的对象

      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {//如果是空的就不会内存泄露
        String className = leakingRef.getClassObj().getClassName();
        return noLeak(className, since(analysisStartNanoTime));
      }
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);//找到这个路劲
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

我们看到findLeakingReference这个方法也是最重要的方法,其实就是遍历查找key值如果找到了那么就说明是内存泄露了

private Instance findLeakingReference(String key, Snapshot snapshot) {
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    if (refClass == null) {
      throw new IllegalStateException(
          "Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");
    }
    List<String> keysFound = new ArrayList<>();
    for (Instance instance : refClass.getInstancesList()) {
      List<ClassInstance.FieldValue> values = classInstanceValues(instance);
      Object keyFieldValue = fieldValue(values, "key");
      if (keyFieldValue == null) {
        keysFound.add(null);
        continue;
      }
      String keyCandidate = asString(keyFieldValue);
      if (keyCandidate.equals(key)) {
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  }

我们再返回去看findLeakTrace这个方法这个其实就是返回泄露的大小和位置
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef, boolean computeRetainedSize) {

    listener.onProgressUpdate(FINDING_SHORTEST_PATH);
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);

    String className = leakingRef.getClassObj().getClassName();

    // False alarm, no strong reference path to GC Roots.
    if (result.leakingNode == null) {
      return noLeak(className, since(analysisStartNanoTime));
    }

    listener.onProgressUpdate(BUILDING_LEAK_TRACE);
    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

    long retainedSize;
    if (computeRetainedSize) {

      listener.onProgressUpdate(COMPUTING_DOMINATORS);
      // Side effect: computes retained size.
      snapshot.computeDominators();

      Instance leakingInstance = result.leakingNode.instance;

      retainedSize = leakingInstance.getTotalRetainedSize();///计算空间大小

      // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
      if (SDK_INT <= N_MR1) {
        listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
        retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
      }
    } else {
      retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
    }

    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));//
  }
;