源码分析-leakCanary

前言

  1. Reference 把内存分为 4 种状态,Active 、 Pending 、 Enqueued 、 Inactive

    1
    2
    3
    4
    >+ Active 一般说来内存一开始被分配的状态都是 Active
    >+ Pending 快要放入队列(ReferenceQueue)的对象,也就是马上要回收的对象
    >+ Enqueued 对象已经进入队列,已经被回收的对象。方便我们查询某个对象是否被回收
    >+ Inactive 最终的状态,无法变成其他的状态
  2. ReferenceQueue

    引用队列,在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中

  3. 如何检测一个对象是否被回收

1
2
3
创建一个引用队列 queue
创建 Reference 对象(通常用弱引用)并关联引用队列
在 Reference 被回收的时候,Reference 会被添加到 queue 中
1
2
3
4
5
ReferenceQueue queue = new ReferenceQueue();  
WeakReference reference = new WeakReference(new Object(), queue);
System.gc();
Reference reference1 = queue.remove();

在 Reference 类加载的时候,Java 虚拟机会会创建一个最大优先级的后台线程,这个线程的工作就是不断检测 pending 是否为 null,如果不为 null,那么就将它放到 ReferenceQueue。因为 pending 不为 null,就说明引用所指向的对象已经被 GC,变成了不可达。

原理

弱引用: 在垃圾回收时,无论内存是否充足,都会将弱引用包装的对象回收。

当JVM进行垃圾回收时,无论内存是否充足,如果该对象只有弱引用存在,那么该对象会被垃圾回收器回收,同时该引用会被加入到关联的ReferenceQueue。因此程序可以通过判断引用队列中是否已经包含指定的引用,来了解被引用的对象是否被GC回收 (引用队列中存在指定的弱引用,说明对象被回收)。

所以 Leakcanary 在进行内存泄露的监控时,利用弱引用的上述特性,在对象生命周期结束后主动gc并检查该对象的弱引用是否被回收,如果弱引用没有被正常回收,说明在对象生命周期结束以后,该对象还被其他对象持有他的非弱引用。该对象还有到达GC ROOTS的可达路径。如果在对象生命周期结束后弱引用不存在了,说明该对象已经被JVM的垃圾回收器正常回收了,该对象的内存空间也被正常回收。

  1. 监听 Activity 的生命周期。
  2. 在 onDestory 的时候,创建对应的 Actitity 的 Refrence 和 相应的 RefrenceQueue,启动后台进程去检测。
  3. 一段时间后,从 RefrenceQueue 中读取,如果有这个 Actitity 的 Refrence,那么说明这个 Activity 的 Refrence 已经被回收,但是如果 RefrenceQueue 没有这个 Actitity 的 Refrence 那就说明出现了内存泄漏。
  4. dump 出 hprof 文件,找到泄漏路径。

生命周期监测

onDestory 时候对activity进行监测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ActivityRefWatcher.class
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
// 通过 Application 注册 Activity 生命周期回调接口。
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}


private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
// 在Activity 被销毁的时候去进行监听(Activity被销毁后,理应被回收,所以在这里触发监听操作)。
refWatcher.watch(activity);
}
};

主线程空闲

AndroidWatchExecutor

1
2
3
4
5
6
7
8
9
private 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);
return false;
}
});
}

监测泄漏

RefWatcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 1.先进行移除(可能这里已经发生过gc操作了)
removeWeaklyReachableReferences();
// Debug 模式忽略
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 2.判断 retainedKeys 中是否存在指定的 key,gone()返回true 表示不存在。
if (gone(reference)) {
return DONE;
}
// 3.前面表示指定的弱引用没有被回收,因此手动触发GC操作。
gcTrigger.runGc(); //内部触发 Runtime.getRuntime().gc() 操作,同时让当前线程sleep(100,给gc一段处理时间)。
// 4.与步骤1相同的操作
removeWeaklyReachableReferences();
// 5.如果gone()返回false,表示可能存在内存泄漏。
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 6.导出堆栈信息,内部其实会触发 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()) 方法导出HProf文件。
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 7.将导出的信息封装成HeapDump对象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// 8.对HeapDump进行内存泄漏的分析
heapdumpListener.analyze(heapDump); // heapdumpListeners是ServiceHeapDumpListener类型
}
return DONE;
}

  1. 先进行移除(可能这里已经发生过gc操作了)。
  2. 判断 retainedKeys 中是否存在指定的 key,gone()返回true 表示不存在。
  3. 前面表示指定的弱引用没有被回收,因此手动触发GC操作。与步骤1相同的操作。
  4. 如果gone()返回false,表示可能存在内存泄漏。
  5. 导出堆栈信息,内部其实会触发 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()) 方法导出HProf文件。
  6. 将导出的信息封装成HeapDump对象。
  7. 对HeapDump进行内存泄漏的分析。

在launcher的显示

1
2
3
4
5
6
7
8
9
<activity
android:theme="@style/leak_canary_LeakCanary.Base"
android:name=".internal.DisplayLeakActivity"
android:process=":leakcanary"
android:enabled="false"
android:label="@string/leak_canary_display_activity_label"
android:icon="@mipmap/leak_canary_icon"
android:taskAffinity="com.squareup.leakcanary.${applicationId}"
>
1
2
3
4
5
6
7
8
public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}