LeakCanary源码学习

LeakCanary项目是quare公司为Java&Android开发提供的一个自动检测内存泄漏的工具,项目地址:https://github.com/square/leakcanary。LeakCanary可以为开发提高代码质量,减少不必要的内存泄漏。尽管Java有垃圾回收机制,但是一些无意识的代码经常会导致对象的引用存在超过其生命周期,Android常见的如activity,fragment等。

How does it work?

1.RefWatcher.watch()创建一个KeyedWeakReference去检测对象;
2.接着,在后台线程,它将会检查是否有引用在不是GC触发的情况下需要被清除的;
3.如果引用引用仍然没有被清除,将会转储堆到.hprof文件到系统文件中(it them dumps the heap into a .hprof file stored on the app file system.)
4.HeapAnalyzerService是在一个分离的进程中开始的,HeapAnalyzer通过使用HAHA解析heap dump;
5.由于一个特殊的引用key和定位的泄露引用,HeapAnalyzer可以在heap dump中找到KeyedWeakReference;
6.如果有一个泄露,HeapAnalyzer计算到GC Roots的最短的强引用路径,然后创建造成泄露的引用链;
7.结果在app的进程中传回到DisplayLeakService,并展示泄露的通知消息;

温馨提醒:LeakCanary分析内存泄露的前提是需要知道一个对象的确切生命周期已经结束,并在我们认为其生命周期应该结束的时间点进行watch。对于Android平台来说,常见的场景就是Activity、Fragment等对象的onDestory中。

LeakCanary项目结构

LeakCanary包含以下5个子项目
1.LeakCanary-analyzer 内存堆分析
2.LeakCanary-watcher 对象应引用监测
3.LeakCanary-andorid android基本配置
4.LeakCanary-andorid-no-op 空壳包,与LeakCanary-andorid结构一模一样,但没有具体实现代码
5.LeakCanary-sample

android基本配置源码分析

1.android 基本配置

1
2
3
4
5
6
7
8
9
10
11
 public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
enabledStrictMode();
LeakCanary.install(this);
}

2.LeakCanary.java部分关键源码–>创建建造者的监测对象AndroidRefWatcherBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 /**
* 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)
//LeakCanary提供了AndroidExcludedRefs来灵活控制是否需要将一些对象排除在考虑之外,因为在Android Framework层自身也存在一些内存泄漏,对于开发者来说这些泄漏是我们无能为力的,所以在AndroidExcludedRefs中定义了很多排除考虑的类。
.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);
}

3.AndroidRefWatcherBuilder.java部分关键源码–>配置ActivityRefWatcher

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/

public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
//建立监听
ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
}
return refWatcher;
}

4.ActivityRefWatcher.java 部分关键代码

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
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
if (SDK_INT < ICE_CREAM_SANDWICH) {
//对于4.0以下版本的需要在基础Activity的onDestroy()进行监听
}
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
activityRefWatcher.watchActivities();
}


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) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};


void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}

~~~~android基本配置完~~~~

对象内存泄露基本监测源码分析—-Watch

1.RefWatcher.java 部分关键代码

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

RefWatcher(...){
//创建引用队列
queue = new ReferenceQueue<>();
}

public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
//创建唯一标识的key
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
//KeyedWeakReference参考了java.util.WeakHashMap实现原理,继承WeakReference,参数queue是一个引用队列管理,queue队列的填充信息由VM传递确认
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() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
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;
}
gcTrigger.runGc(); //还没有回收,就手动GC
removeWeaklyReachableReferences(); //再判断检测的弱引用回收了没
if (!gone(reference)) { //还没有回收,怀疑内存泄露
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

File heapDumpFile = heapDumper.dumpHeap(); //dump内存快照,精确分析内存堆
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);
}
}
}

2.KeyedWeakReference.java部分源码分析
KeyedWeakReference的父类为Reference,Reference可以实现与VM的信息交流,特别是GC的相关信息交流

Reference是引用对象的抽象基类。定义了所有引用对象的通用行为。当被 Reference 引用的对象的生命周期结束,一旦被 GC 检查到,GC 将会把该对象添加到 ReferenceQueue 中,待ReferenceQueue处理。当 GC 过后对象一直不被加入 ReferenceQueue,它可能存在内存泄漏。
Reference.java部分关键源码,android sdk的referent和java sdk 的referent的代码有一定出入,但实现原理都是一致的,以下主要以比较全面的java sdk的源码进行分析:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

//基本数据结构
volatile T referent;
volatile ReferenceQueue<? super T> queue;
volatile Reference queueNext;


/**
* Used internally by the VM. This field forms a circular and
* singly linked list of reference objects discovered by the
* garbage collector and awaiting processing by the reference
* queue thread.
*由VM赋值,如果在虚拟机hotspot的openjdk源码hotspot/src/share/vm/memory/referenceProcessor.cpp里面的方法ReferenceProcessor::discover_reference可以看到pendingNext的赋值,当Reference内部的referent对象的可达状态改变时,jvm会将Reference对象放入pending链表。
* @hide
*/

public volatile Reference<?> pendingNext;
transient private Reference<T> discovered;

private static class ReferenceHandler extends Thread {

private static void ensureClassInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
}

static {
// pre-load and initialize InterruptedException and Cleaner classes
// so that we don't get into trouble later in the run loop if there's
// memory shortage while loading/initializing them lazily.
ensureClassInitialized(InterruptedException.class);
ensureClassInitialized(Cleaner.class);
}

ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}

public void run() {
while (true) {
tryHandlePending(true);
}
}
}

static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
//如果VM赋值给了pending
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}

// Fast path for cleaners
if (c != null) {
c.clean();
return true;
}

//如果Q不为空,把引用放入Queue
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}

static {
//取得当前线程组
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
//设置最高优先级
handler.setPriority(Thread.MAX_PRIORITY);
//标记守护线程或用户线程
handler.setDaemon(true);
handler.start();

// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}

对象内存泄露内存堆源码分析—-Haha analyzer

Haha 是square公司另外一个开源的分析内存工具,是LeakCanary项目里面确认内存泄露对象以及对象路劲的核心部分。作用为分析内存堆的hprof文件。VM 会有堆内各个对象的引用情况,并能以hprof文件导出。Haha分析 hprof 文件生成Snapshot对象。Snapshot用以查询对象的最短引用链。

参考资料

leakcanary github wiki
Wrangling Dalvik: Memory Management in Android (Part 1 of 2)
Hunting Your Leaks: Memory Management in Android (Part 2 of 2)
Investigating Your RAM Usage

文章目录
  1. 1. How does it work?
  2. 2. LeakCanary项目结构
    1. 2.1. android基本配置源码分析
    2. 2.2. 对象内存泄露基本监测源码分析—-Watch
    3. 2.3. 对象内存泄露内存堆源码分析—-Haha analyzer
  • 参考资料
  • ,