LeakCanary 源码分析及借鉴

煤矿中的金丝雀

LeakCanary 是一个帮我们在 Android 或 Java 项目开发时检测内存泄漏的库。

本文以 LeakCanary-1.5.1 版本为例,分析 LeakCanary 原理,以及借鉴其中的方法。

关于它的图标有一个故事:

17 世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为 “瓦斯检测指标”,以便在危险状况下紧急撤离。

内存泄漏检测的基本原理

给弱引用关联一个引用队列,当弱引用持有内容被 gc 回收后,该弱引用会被添加到关联的引用队列中。

试试下面的代码,注释掉user=null;和不注释掉,看看有什么不同

public class RefTest {
public static void main(String[] args) {
//user为强引用
User user = new User("张三", 18);
//创建弱引用并关联引用队列
ReferenceQueue<User> queue = new ReferenceQueue<>();
WeakReference<User> weakReference = new WeakReference<User>(user,queue);
//置空强引用,触发GC
user=null;
Runtime.getRuntime().gc();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//强制系统回收已经没有强引用的对象
System.runFinalization();

WeakReference pollRef=null;
//弹出对列的引弱用
while ((pollRef = (WeakReference) queue.poll()) != null) {
System.out.println("pollRef的内存地址:"+pollRef.toString());
System.out.println("pollRef等于weakReference?:"+weakReference.equals(pollRef));
}
}
}

Java 垃圾回收 (GC)

  • 在 Java 中垃圾判断方法是 可达性分析算法,这个算法的基本思路是通过一系列的”GC Root” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到 GC Root 没有任何引用链相连时,则证明此对象是不可用的。
  • GC Root 的对象包括以下几种:
    • 1、虚拟机栈中引用的对象。
    • 2、方法区中类静态属性引用的对象。
    • 3、方法区中常量引用的对象。
    • 4、本地方法栈中 JNI 引用的对象。
  • 就算一个对象,通过可达性分析算法分析后,发现其是『不可达』的,也并不是非回收不可的。一般情况下,要宣告一个对象死亡,至少要经过两次标记过程:
    • 1、经过可达性分析后,一个对象并没有与 GC Root 关联的引用链,将会被第一次标记和筛选。筛选条件是此对象有没有必要执行 finalize() 方法。如果对象没有覆盖 finalize() 方法,或者已经执行过了。那就认为他可以回收了。如果有必要执行 finalize() 方法,那么将会把这个对象放置到 F-Queue 的队列中,等待执行。
    • 2、虚拟机会建立一个低优先级的 Finalizer 线程执行 F-Queue 里面的对象的 finalize() 方法。如果对象在 finalize() 方法中可以『拯救』自己,那么将不会被回收,否则,他将被移入一个即将被回收的 ReferenceQueue。

LeakCanary 使用

LeakCanary 在 Application 初始化,代码如下:

public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//检查当前进程是否在HeapAnalyzerService所属进程
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
//安装泄露检测
LeakCanary.install(this);
}
}

编译成功后,可以在 debug 状态下的 AndroidManifest.xml 文件中,看到 LeakCanary 注册的组件。

<!-- 这个是LeakCanary分析泄露的Service -->
<service
android:name="com.squareup.leakcanary.internal.HeapAnalyzerService"
android:enabled="false"
android:process=":leakcanary" />
<!-- 这个是LeakCanary展示泄露的Service -->
<service
android:name="com.squareup.leakcanary.DisplayLeakService"
android:enabled="false"/>
<activity
android:name="com.squareup.leakcanary.internal.DisplayLeakActivity"
android:enabled="false"
android:icon="@mipmap/leak_canary_icon"
android:label="@string/leak_canary_display_activity_label"
android:taskAffinity="com.squareup.leakcanary.com.pengguanming.studydemo"
android:theme="@style/leak_canary_LeakCanary.Base" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

其中 DisplayLeakActivity 被设置为 Launcher,并设置了金丝雀的 icon,这也就是为什么使用 LeakCanary 会在桌面上生成 icon 入口的原因。但是,这里要注意 DisplayLeakActivity 的 enable 属性被设置为 false 了,默认在桌面上是不会显示入口的。

监听 Activity 销毁的方法

返回来看 LeakCanary.install(this) 方法

/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}

/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}

install 方法通过 AndroidRefWatcherBuilder,构造了一个 RefWatcher 对象,再看 buildAndInstall() 方法:

public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}

ActivityRefWatcher.install() 调用了 watchActivities() 方法。

public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}

watchActivities() 通过传过来的 application 对象注册了一个 registerActivityLifecycleCallbacks。

public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

这个监听只监听了 onActivityDestroyed() 方法

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

上面就是 LeakCanary 注册监听 Activity onDestroy() 生命周期的过程

检测对象弱引用关联引用队列

前面提到,弱引用关联一个引用队列,当弱引用保存的引用被释放掉后,弱引用就会进入到引用队列中,LeakCanary 当然也是用这种方式,不过它更聪明。

进入到 refWatcher.watch() 方法中:

public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
// 这里的 watchedReference 就是一个 Activity
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
// 为每个 watchedReference 生成一个随机key,将key添加到retainedKeys中
retainedKeys.add(key);
// 每次创建一个弱引用,并关联一个引用队列
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);

ensureGoneAsync(watchStartNanoTime, reference);
}

上面的 retainedKeys 是一个支持并发访问的 CopyOnWriteArraySet 对象。
这个 KeyedWeakReference 里的 key 值就是 LeakCanary 聪明的地方了

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"));
// 比普通 WeakReference 多了两个参数,其中的 key 最为关键
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}

为什么要多保存一个 key 值呢?它在哪里用到了呢?继续往下看:

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
// 这里逻辑先不考虑,直接看 ensureGone()
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
// 计算watch方法到gc垃圾回收的时长
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 尝试移除已经到达引用队列的弱引用
removeWeaklyReachableReferences();

if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
// 手动gc
gcTrigger.runGc();
// gc后,再次移除引用队列中的弱引用
removeWeaklyReachableReferences();
if (!gone(reference)) {
// 弱引用没有被移除,说明内存泄漏了
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 确定泄漏后,生成prof文件
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;
}

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

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

在 removeWeaklyReachableReferences() 方法中,将 queue 队列里所有弱引用弹栈。如果出栈了,就移除掉 retainedKeys 中保存的 key。

在 gone() 方法判断,如果 retainedKeys 依然包含 reference 的 key,说明该 reference 没有进入 queue 队列,也就是没有释放掉对象的引用,所以很可能是发生了内存泄漏。

所以 KeyedWeakReference 中保存的 key 值,就是为了方便判断当前的弱引用是否发生了内存泄漏。

LeakCanary 的线程调度

刚才再看刚才忽略的 watchExecutor.execute() 方法,watchExecutor 是一个接口,它的实现类是 AndroidWatchExecutor。

public final class AndroidWatchExecutor implements WatchExecutor {

static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
private final Handler mainHandler;
private final Handler backgroundHandler;
private final long initialDelayMillis;
private final long maxBackoffFactor;

public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}

@Override public void execute(Retryable retryable) {
// 最终都会切换到主线程,调用waitForIdle()方法
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}

void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
// 切换到主线程
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}

void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
// 在主线程执行下面代码
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
// 方法如果返回 false,这个 IdleHandler 只会执行一次
return false;
}
});
}

void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
// 设置延迟发送时间
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
// 子线程运行一个runnable
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}

AndroidWatchExecutor 类中,有两个 handler,一个在 main thread,一个在 HandlerThread 的子线程中。外部调用 execute() 方法,最终都会在主线程调用 waitForIdle() 方法。

waitForIdle() 运行在主线程,会在主线程添加一个 IdleHandler。

IdleHandler 的妙用

IdleHandler,这是一种在只有当消息队列没有消息时或者是队列中的消息还没有到执行时间时才会执行的 IdleHandler,它存放在 mPendingIdleHandlers 队列中。

关于 IdleHandler 如何使用,这篇文章提供了一个非常好的方法:你知道 android 的 MessageQueue.IdleHandler 吗?

简单总结下这篇文章:

  • 1. 使用 HandlerThread,利用 HandlerThread 绑定的子线程 Handler,实现单线程队列处理数据 + 异步线程接收数据更新
  • 2. 反射 HandlerThread,得到 MessageQueue 对象,调用 MessageQueue.addIdleHandler()
  • 3. 在 IdleHandler 的 queueIdle() 回调方法里通知数据改变

这样做有两个好处:

  • 1.HanlderThread 按加入顺序操作数据,不需要对数据加锁
  • 2. 对 HandlerThread 的 MessageQueue 调用 addIdleHandler(),不管操作了多少遍数据,只会在所有操作完成后(idle),通知数据的监听者,避免频繁更新 UI

AndroidWatchExecutor 的作用就是,在主线程空闲的时候才会调用,并且是延迟调用子线程的 retryable.run() 方法。这样可以保证不影响主线程的流畅度。

手动触发 GC

上面的 retryable.run() 运行在子线程,说明 ensureGone() 就是在子线程。在看一下 LeakCanary 是如何调用 GC 的:

public interface GcTrigger {
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();
enqueueReferences();
System.runFinalization();
}

private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};

void runGc();
}

gcTrigger.runGc() 先会调用 Runtime.getRuntime().gc() 以触发系统 gc 操作,然后当前后台子线程 sleep 100 毫秒,最后调用 System.runFinalization() 强制系统回收没有引用的队列,这样确保引用对象是否真的被回收了。

泄露信息分析

上面 heapDumper.dumpHeap() 方法的具体实现是在 AndroidHeapDumper 类中:

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
@Override public File dumpHeap() {
// 创建一个XXX_pending.hprof文件,这种文件最多7个
// 文件存储路径为 /storage/emulated/0/Download/leakcanary-xxx(package-name)/
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

if (heapDumpFile == RETRY_LATER) {
// 文件创建失败
return RETRY_LATER;
}

FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);

if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}

Toast toast = waitingForToast.get();
try {
// 向文件写入内容
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}

private void showToast(final FutureResult<Toast> waitingForToast) {
mainHandler.post(new Runnable() {
@Override public void run() {
final Toast toast = new Toast(context);
toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
LayoutInflater inflater = LayoutInflater.from(context);
toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
toast.show();
// Waiting for Idle to make sure Toast gets rendered.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
waitingForToast.set(toast);
return false;
}
});
}
});
}

private void cancelToast(final Toast toast) {
mainHandler.post(new Runnable() {
@Override public void run() {
toast.cancel();
}
});
}

这里的 FutureResult,会阻塞当前后台子线程,并监听主线程是否空闲,若 5 秒内不空闲,则返回 RETRY_LATER。
若主线程空闲则会调用 waitingForToast.set(toast),不在阻塞后台子线程,调用 android.os.Debug.dumpHprofData() 生成. prof 文件。

Debug.dumpHprofData 会导致线程阻塞。因为 dumpheap 的操作是在应用进程的主线程中进行操作,本质上是在该应用进程的虚拟机中进行,dumpheap 时应用进程会 block 住,如过 heap 文件过大很容易导致应用进程操作界面卡住,如果此时再进行点击或滑动等操作极易再抛出 anr 等弹窗,用户体验极差。

这里介绍下 FutureResult 的实现:

public final class FutureResult<T> {
// AtomicReference实现对象引用的原子更新
private final AtomicReference<T> resultHolder;
// 同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
private final CountDownLatch latch;

public FutureResult() {
resultHolder = new AtomicReference<>();
latch = new CountDownLatch(1);
}

public boolean wait(long timeout, TimeUnit unit) {
try {
return latch.await(timeout, unit);
} catch (InterruptedException e) {
throw new RuntimeException("Did not expect thread to be interrupted", e);
}
}

public T get() {
if (latch.getCount() > 0) {
throw new IllegalStateException("Call wait() and check its result");
}
return resultHolder.get();
}

public void set(T result) {
resultHolder.set(result);
latch.countDown();
}
}

关于 CountDownLatch 的介绍参考:Java 并发学习之 CountDownLatch 实现原理及使用姿势

prof 文件分析

关于 prof 文件的分析,还要回到 RefWatch.ensureGone() 方法中:

heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));

最终会调用到

HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);

// 在HeapAnalyzerService 中
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 继承自 IntentService。在前面的 AndroidManifest 中可以看到,HeapAnalyzerService 是运行在单独的进程中。

在 HeapAnalyzerService 的 onHandleIntent 方法中接收和处理传递过来的 dump 文件。

@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的静态方法,将结果返回
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}

再看下在 heapAnalyzer.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 {
// 使用HAHA库
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
// 这里使用了THashMap,去掉重复路径
deduplicateGcRoots(snapshot);
// 根据引用key找到泄漏对象
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));
}
// 返回一个AnalysisResult对象
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}

在 heapAnalyzer.checkForLeak() 方法中引入 HAHA 库 (一个 heap prof 堆文件分析库),将 hprof 文件解析成内存快照 Snapshot 对象进行分析。
还使用 jetBrains 的 THashMap(THashMap 的内存占用量比 HashMap 小) 做中转,去掉 snapshot 中 GCRoot 的重复路径,以减少内存压力。

返回分析结果

在 HeapAnalyzerService 的 onHandleIntent 方法中,调用了

AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);

// 在AbstractAnalysisResultService 中
public static void sendResultToListener(Context context, String listenerServiceClassName,
HeapDump heapDump, AnalysisResult result) {
Class<?> listenerServiceClass;
try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);
}

启动 DisplayLeakService,并将结果返回。DisplayLeakService 运行在主线程,继承自 AbstractAnalysisResultService,也是一个 IntentService。
在 AbstractAnalysisResultService 的 onHandleIntent() 接收结果,并回调 onHeapAnalyzed() 方法,创建并显示一个通知。

具体的结果分析和处理,可以看这篇文章:LeakCanary 源码分析第三讲-HeapAnalyzerService 详解

内存泄漏信息保存和分析的大概流程是:

  • 通过 Debug.dumpHprofData(),生成一个 xxx_pending.hprof 文件
  • 使用 HAHA 库(github.com/square/haha),将 hprof 文件解析成内存快照 Snapshot 对象进行分析
  • 使用 jetBrains 的 THashMap 做中转,去掉 snapshot 中 GCRoot 的重复路径,以减少内存压力。
  • 找出泄露对象并找出泄露对象的最短路径
  • 得到结果后,重命名 xxx_pending.hprof 文件为 yyyy-MM-dd_HH-mm-ss_SSS.hprof
    • 将 hprof 文件内容和分析结果都保存到 yyyy-MM-dd_HH-mm-ss_SSS.hprof.result 文件中

以上就是整个 LeakCanary 捕获内存泄漏,并展示出来的整体流程。

学习借鉴

  • 使用 FutureResult 同步主线程和子线程
  • 使用 CopyOnWriteArraySet 解决并发读写问题
  • 构建者模式,代码简洁、清新,链式调用创建对象,参考 RefWatcherBuilder 对象
  • MessageQueue.addIdleHandler(IdleHandler handler),监听线程空闲
  • 手动 GC,参考 GCTrigger.runGc()。
  • IntentService 跨进程

Ref

https://www.jianshu.com/p/49239eac7a76

https://juejin.im/post/5d09fb7e5188252af2012e73



本文采用知识共享署名 2.5 中国大陆许可协议进行许可,欢迎转载,但转载请注明来自 Agehua’s Blog,并保持转载后文章内容的完整。本人保留所有版权相关权利。

本文链接:http://agehua.github.io/2019/07/30/LeakCanary_source_code/

Share Comments
Error: Comments Not Initialized