LeakCanary 源码深挖

541 阅读8分钟

一、前言

LeakCanaryAndroid 端用于自动检测内存泄漏的开源库,使用这个工具可以方便的监控 ActivityFragment 的内存泄漏情况, 并且提供了可视化界面, 可以在开发过程中很好的暴露和排查问题。基于 LeakCanary 1.5.4 源码。

二、怎样使用?

添加依赖

dependencies {
    ......
    implementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
    ......
}

编写 LeakCanary 使用工具类 LeakCanaryUtil.java

public class LeakCanaryUtil {

    private static RefWatcher sRefWatcher;

    public static void init(@NonNull Application application) {
        if (!BuildConfig.DEBUG) {
            return;
        }
        if (LeakCanary.isInAnalyzerProcess(application)) {
            return;
        }
        sRefWatcher = LeakCanary.install(application);
    }

    public static void watch(@Nullable Object obj) {
        if (sRefWatcher == null || obj == null) {
            return;
        }
        sRefWatcher.watch(obj);
    }

}

Application 中初始化

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        LeakCanaryUtil.init(this);
    }
}

设置内存泄漏监控 BaseFragment.java

public abstract class BaseFragment extends Fragment {
   
    @Override
    public void onDetach() {
        LeakCanaryUtil.watch(this);
    }

}

三、怎样检测到内存泄漏的?

Application 中, LeakCanaryinstall 方法的构建一个 RefWatcher 对象, 并且初始化它

public final class LeakCanary {
    
  public static RefWatcher install(Application application) {
    return refWatcher(application)
               .listenerServiceClass(DisplayLeakService.class)
               .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
               .buildAndInstall();	// 分析 1
  }
    
  public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);	// 分析 2
  }
  
}

分析 1 可以看到这是一个构建者的链式调用, 最终通过 buildAndInstall 来完成对 RefWatcher 的构建和安装。分析 2 可以看到这个 RefWatcher 对象通过 AndroidRefWatcherBuilder.buildAndInstall 创建,那么,我们看一下 AndroidRefWatcherBuilder.buildAndInstall 做了什么。

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
    
  public RefWatcher buildAndInstall() {
    // 通过 build 构建实例对象
    RefWatcher refWatcher = build();	// 分析 1
    if (refWatcher != DISABLED) {
      // 注册内存泄漏监听
      ActivityRefWatcher.install((Application) context, refWatcher);	// 分析 2
    }
    return refWatcher;
  }  
    
}

可以看到 AndroidRefWatcherBuilder.buildAndInstall 中, 主要做了两步操作

  • 通过 build 方法, 创建 RefWatcher 对象
  • 调用 ActivityRefWatcher.install 注册来监听内存泄漏

下面我们接着看分析 1 中 build() 方法,其使用了 builder 设计模式去构建初始化对象

public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
    
  /** Creates a {@link RefWatcher}. */
  public final RefWatcher build() {
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }
    ......
    // 构建 RefWatcher 对象
    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        excludedRefs);
  }  
    
}

public final class RefWatcher {

  public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();

  private final WatchExecutor watchExecutor;
  private final DebuggerControl debuggerControl;
  private final GcTrigger gcTrigger;
  private final HeapDumper heapDumper;
  private final Set<String> retainedKeys;
  private final ReferenceQueue<Object> queue;
  private final HeapDump.Listener heapdumpListener;
  private final ExcludedRefs excludedRefs;

  RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,
      HeapDumper heapDumper, HeapDump.Listener heapdumpListener, ExcludedRefs excludedRefs) {
    // 用于执行监听引用
    this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor");
    // 判断是否在调试中
    this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl");
    // 用于执行 GC
    this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger");
    // 用于将 Heap dump 到文件中
    this.heapDumper = checkNotNull(heapDumper, "heapDumper");
    // 用于接收并分析 heap 信息
    this.heapdumpListener = checkNotNull(heapdumpListener, "heapdumpListener");
    // 排除系统引起的内存泄露
    this.excludedRefs = checkNotNull(excludedRefs, "excludedRefs");
    // 创建集合, 用于保存待分析对象弱引用对应的 key
    retainedKeys = new CopyOnWriteArraySet<>();
    // 创建引用队列, 用于存储待 GC 的元素
    queue = new ReferenceQueue<>();
  }
    
}

在上面 AndroidRefWatcherBuilder.buildAndInstall 的分析 2 中 AndroidRefWatcherBuilder.java

// 注册内存泄漏监听
ActivityRefWatcher.install((Application) context, refWatcher);	// 分析 2
public final class ActivityRefWatcher {

  public static void install(Application application, RefWatcher refWatcher) {
    // 1. 创建 ActivityRefWatcher 对象
    // 2. 调用 ActivityRefWatcher.watchActivities
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }
  
  private final Application application;
  private final RefWatcher refWatcher;
  
  public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
    this.application = checkNotNull(application, "application");
    this.refWatcher = checkNotNull(refWatcher, "refWatcher");
  }

}

可以看到 ActivityRefWatcher.install 中, 首先创建了 ActivityRefWatcher 实例, 然后调用了它的 watchActivities 方法。 接着看下去 ActivityRefWatcher.watchActivities() 的实现

public final class ActivityRefWatcher {
    
    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) {
          // 调用 onActivityDestroyed 来处理 Destroy 之后的 Activity
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };
      
  public void watchActivities() {
    // 确保不会注册两个 lifecycle
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }
  
  void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);	// 分析 1,重点
  }
  
}

可以看到当 Activity 被销毁的回调后, 便会调用 onActivityDestroyed 方法, 进而调用 refWatcher.watch 方法,看到这里,总算知道工作流程了,那么怎样检测到内存泄漏,就是重点内容了,所以我们将目光重点投向 refWatcher.watch(activity) 这一操作。

四、内存泄漏检测原理

public final class RefWatcher {
  private final Set<String> retainedKeys;
  private final ReferenceQueue<Object> queue;

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

  public void watch(Object watchedReference, String referenceName) {
    ......
    // 1. 构建 要监听对象(Activity) 的 key
    String key = UUID.randomUUID().toString();
    // 2. 添加到 key 缓存
    retainedKeys.add(key);
    // 3. 构建要监听对象(Activity)的弱引用
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    // 4. 确认一下被弱引用的对象是否被回收了
    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);
      }
    });
  }
}

可以看出,refWatcher.watch(activity) 为监听对象构建了一个 key, 并且为它构建了一个 弱引用 ,我们都知道,弱引用要是对象要被回收,会将其弱引用添加到引用队列 ReferenceQueue 中,而 LeakCanary 正是也利用了这一原理,进行内存泄漏检测,具体我们继续看下去,看到 ensureGone 方法。

public final class RefWatcher {

  @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    ......
    removeWeaklyReachableReferences();
    ......
    if (gone(reference)) {
      return DONE;
    }
    // 执行 GC
    gcTrigger.runGc();
    // 尝试移除弱可及的引用, 即即将被 GC 的对象
    removeWeaklyReachableReferences();
    // 判断弱引用是否已经被移除了
    if (!gone(reference)) {
      ......
      // Dump Hrof 文件
      File heapDumpFile = heapDumper.dumpHeap();
      ......
      // 分析 Hrof 的文件
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

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

  private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    // 从引用队列中获取即将被 GC 的对象
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      // 既然即将被 GC 了, 那么说明就不会内存泄漏
      // 尝试从 retainedkey 中移除
      retainedKeys.remove(ref.key);
    }
  }
    
}

执行 GC 之后, 这个对象的弱引用依旧存在, 那么就说明可能发生内存泄漏了。 到此,已经可以解析得到 LeakCanary 是怎样检测发生了内存泄漏的了。 发生泄漏后,LeakCanary 会将相关信息显示给开发者,这就需要对 Dump Hrof 文件进行分析了。

五、泄漏分析

从上面 ensureGone 方法内部,检测到内存泄漏时执行了如下的操作:

  1. 通过 heapDumper.dumpHeap() 获取此刻的内存镜像 HPROF 文件
  2. 最终会调用获取 Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
  3. 通过 heapdumpListener.analyze 来分析内存泄漏的引用链

还记得在 install 的时候

public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }
public AndroidRefWatcherBuilder listenerServiceClass(
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

这里的 listenerServiceClass(DisplayLeakService.class) 中,heapDumpListenerServiceHeapDumpListener 实例构造了参数,heapdumpListener.analyzeheapdumpListener 正是 ServiceHeapDumpListener。 那 heapdumpListener.analyze 是怎样分析的呢?我们看到

public final class ServiceHeapDumpListener implements HeapDump.Listener {

  ......

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

public final class HeapAnalyzerService extends IntentService {

  private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  private static final String HEAPDUMP_EXTRA = "heapdump_extra";

  public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    // 通知 HeapAnalyzerService 远程服务处理这个 Intent
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

heapdumpListener.analyze 会通知远程的 HeapAnalyzerService 服务, 解析 HPROF 文件,接着看 HeapAnalyzerService

public final class HeapAnalyzerService extends IntentService {

  @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);
   
    // 1. 创建了 HeapAnalyzer 对象, 用于分析 HPROF 文件
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    // 2. 执行泄漏引用链的分析
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    ......
  }
  
}

HeapAnalyzer 就是内存泄漏引用链分析的核心所在了, 下面我们看看它的 heapAnalyzer.checkForLeak 方法实现

public final class HeapAnalyzer {
    
  /**
   * Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
   * and then computes the shortest strong reference path from that instance to the GC roots.
   */
  public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();
    ......
    try {
      // square haha 提供技术实现
      // 1. 将文件映射到内存, 内部实现很有意思, 将文件流读入 ByteBuffer 的数组集合
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      // 2. 构建 HPROF 数据到 Snapshot 对象中
      HprofParser parser = new HprofParser(buffer);
      Snapshot snapshot = parser.parse();
      // 3. 从 Snapshot 获取 Java 的 GC Roots
      deduplicateGcRoots(snapshot);
      // 校验在我们 dump Hrof 期间, 这个对象是否被回收了
      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));
      }
      // 4. 从 GC Roots 中找寻到泄漏对象的引用链
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }
    
}

可以看到,HeapAnalyzercheckForLeak 的实现主要做了以下工作

  1. 解析 Hprof 文件到 Snapshot 对象中
  2. Snapshot 中找寻 GC Roots
  3. GC Roots 中找寻到泄漏对象的引用链

那么 GC Roots 中如何找寻到泄漏对象的引用链呢?

六、GC Roots 中找寻到泄漏对象的引用链

public class Snapshot {

    public final void addRoot(@NonNull RootObj root) {
        mCurrentHeap.addRoot(root);
        root.setHeap(mCurrentHeap);
    }

   // addRoot 调用的地方是以下六个方法
    private int loadJniLocal(){...}

    private int loadJniMonitor() {...}

    private int loadThreadBlock() {...}
    
    private int loadBasicObj(RootType type) {...}
    
    private int loadNativeStack() {...}
    
    private int loadJavaFrame() {...}

}

public class Heap {

    //  Root objects such as interned strings, jni locals, etc
    @NonNull
    ArrayList<RootObj> mRoots = new ArrayList<RootObj>();

    public final void addRoot(@NonNull RootObj root) {
        root.mIndex = mRoots.size();
        mRoots.add(root);
    }
    
}

Snapshot snapshot = parser.parse(); 这一步操作中 将 GC Roots 对象添加到了 Snapshot 其中。 我们再回归到 HeapAnalyzercheckForLeak 方法中,调用了 findLeakTrace 方法,这个方法是查找引用链

public final class HeapAnalyzer {

  private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef) {
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    // 找寻引用链
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
    ......
  }
  
}

可以看到调用了 ShortestPathFinderfindPath 查找引用链

final class ShortestPathFinder {
    
    
  Result findPath(Snapshot snapshot, Instance leakingRef) {
    clearState();
    canIgnoreStrings = !isString(leakingRef);
    // 将 snapshot 构建成邻接表的图结构
    enqueueGcRoots(snapshot);

    boolean excludingKnownLeaks = false;
    LeakNode leakingNode = null;
    // 使用图的广度优先遍历, 搜索距离 GC Roots 最近的引用链
    while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
      LeakNode node;
      if (!toVisitQueue.isEmpty()) {
        node = toVisitQueue.poll();
      } else {
        node = toVisitIfNoPathQueue.poll();
        ......
        excludingKnownLeaks = true;
      }

      // 找到了目标的结点, 终止搜索
      if (node.instance == leakingRef) {
        leakingNode = node;
        break;
      }

      if (checkSeen(node)) {
        continue;
      }

      if (node.instance instanceof RootObj) {
        visitRootObj(node);
      } else if (node.instance instanceof ClassObj) {
        visitClassObj(node);
      } else if (node.instance instanceof ClassInstance) {
        visitClassInstance(node);
      } else if (node.instance instanceof ArrayInstance) {
        visitArrayInstance(node);
      } else {
        throw new IllegalStateException("Unexpected type for " + node.instance);
      }
    }
    return new Result(leakingNode, excludingKnownLeaks);
  }
    
}

此处使用图的广度优先遍历, 搜索距离 GC Roots 最近的引用链,找到了则结束搜索。

七、总结

在我们实际开发过程中,会发现 LeakCanary 还是会出现误判的情况,监测逻辑是异步的,如果判断 Activity 是否可回收时某个 Activity 正好还被某个方法的局部变量持有,就会引起误判,误判应该怎样解决呢?这就要理解了其实现原理了才好下手解决了,改进的方案有很多种,例如可以在发现某个 Activity 无法被回收,再重复判断多次,以防在判断时该 Activity 被局部变量持有导致误判,具体的实现方式文章不作详细介绍了。到此,LeakCanary 源码分析完毕啦,欢迎交流、指正。

参考:

square.github.io/leakcanary/ github.com/Tencent/mat… sharrychoo.github.io/blog/