LeakCanary
是 Android
端用于自动检测内存泄漏的开源库,使用这个工具可以方便的监控 Activity
和 Fragment
的内存泄漏情况, 并且提供了可视化界面, 可以在开发过程中很好的暴露和排查问题。基于 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
中, LeakCanary
的 install
方法的构建一个 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 {
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> {
/** 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 retainedKeys;
private final 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 retainedKeys;
private final ReferenceQueue
可以看出,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
方法内部,检测到内存泄漏时执行了如下的操作:
heapDumper.dumpHeap()
获取此刻的内存镜像 HPROF
文件Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
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)
中,heapDumpListener
将 ServiceHeapDumpListener
实例构造了参数,heapdumpListener.analyze
的 heapdumpListener
正是 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));
}
}
}
可以看到,HeapAnalyzer
中 checkForLeak
的实现主要做了以下工作
Hprof
文件到 Snapshot
对象中Snapshot
中找寻 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 mRoots = new ArrayList();
public final void addRoot(@NonNull RootObj root) {
root.mIndex = mRoots.size();
mRoots.add(root);
}
}
在 Snapshot snapshot = parser.parse();
这一步操作中 将 GC Roots
对象添加到了 Snapshot
其中。
我们再回归到 HeapAnalyzer
中 checkForLeak
方法中,调用了 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);
......
}
}
可以看到调用了 ShortestPathFinder
的 findPath
查找引用链
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
源码分析完毕啦,欢迎交流、指正。
参考:
https://square.github.io/leakcanary/
https://github.com/Tencent/matrix/wiki/Matrix-Android-ResourceCanary
https://sharrychoo.github.io/blog/