本文首要内容
- 1、Reference 简介
- 2、LeakCanary 运用
- 3、LeakCanary 源码剖析
LeakCanary ,一种常见的内存走漏剖析东西,它能剖析出内存走漏点并以告知方式告知运用者,运用也比较简单,但功能强大。
笔者第一次见到 LeakCanary 时,并不清楚它的原理,了解到它仅仅一个开源工程并不是官方东西之后,觉得作者太牛逼了,内存走漏都能够检测。今天咱们一起来看 LeakCanary 的源码,揭开它的奥秘面纱。
1、Reference 简介
java中存在四种引证,从头温习一遍四种引证的用法及作用:
-
强引证:最遍及的引证,声明一个变量便是强引证,比如 obj ,当它被置为null时,该目标可能会被 JVM 收回,由于还要看是否有其它强引证指向它。
Object obj = new Object();
-
SoftReference,软引证:当内存不够用的时分,才会收回软引证
-
WeakReference,弱引证: new出来的目标没有强引证衔接时,下一次GC时,就会收回该目标。
-
PhantomReference,虚引证 : 与要与ReferenceQueue配合运用,它的get()办法永远回来null
SoftReference、WeakReference、PhantomReference等类都位于 java.lang.ref 包中,它们都有一个共同的父类,Reference 。
java.lang.ref包下首要都是reference相关的类,首要包括:
- FinalReference: 代表强引证,使无法直接运用。
- Finalizer:FinalReference的子类,首要处理finalize相关的工作
- PhantomReference: 虚引证
- Reference: 引证基类,abstract的
- ReferenceQueue: 引证轨道行列
- SoftReference:软引证
- WeakedReference: 弱引证
下面,阐述关于Reference 相关的一个结论:
Reference引证的目标被收回时,Reference 目标将被增加到 ReferenceQueue中,前提是结构 Reference 时,参数中有 ReferenceQueue。
假如要监听某个目标是否被收回,有什么办法呢?
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakedReference r = new WeakedReference(ojb, queue);
依据上面的结论,假如 obj 目标被收回了,那么 queue 将增加 r,那么咱们能够查找行列,假如有r,则证明 obj 目标被收回了,监控完成。
检查下 Reference 的源码,它里面的要害代码如下:
// 静态变量,pending
private static Reference pending = null;
// 线程,不停地收回 pending
private static class ReferenceHandler extends Thread {
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
for (;;) {
Reference r;
synchronized (lock) {
if (pending != null) {
r = pending;
Reference rn = r.next;
pending = (rn == r) ? null : rn;
r.next = r;
} else {
try {
lock.wait();
} catch (InterruptedException x) { }
continue;
}
}
// Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
}
// 将 r 增加到 ReferenceQueue 中
ReferenceQueue q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
}
看到这里,可能有同学会发问,pending 在哪被赋值的?怎样知道它便是要被收回的 Reference 呢?确实,这个问题我也没有从源码中查到,从网上找的材料来看,都说 pending 由 JVM 保护。Reference 有个成员变量next,它能够很轻松地变成一个链表,而pending 正是一个链表的头节点,假如某个 Reference 将被收回,它将被 增加到pending 的链表当中,假如它立刻要被收回了,ReferenceHandler 线程将它增加到 ReferenceQueue 中。
经过 Reference 的学习,感觉 LeakCanary 最大的难题处理了,如何判定一个目标是否被收回了。
2、LeakCanary 运用
LeakCanary 运用十分简单,首要在你项目app下的build.gradle中装备:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
// 可选,假如你运用支持库的fragments的话
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}
然后在你的Application中装备:
public class WanAndroidApp extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// 1
return;
}
// 2
refWatcher = LeakCanary.install(this);
}
运用便是这么得简单。
怎样判别一个目标已死呢?内存可达性算法,即从一个根目标动身,假如无法寻到一条途径指向该目标,则目标已死。
假设是咱们自己来设计 LeakCanary ,咱们会怎样去设计呢?现在现已能够监控某个目标是否现已被收回了。咱们也不可能去监听一切的目标吧,这样不现实,肯定是去找特定目标来监控,在Android中,常见的内存走漏都会导致 activity 无法被收回,activity 便是最特定的目标,所以,能够监听 activity。
LeakCanary 正是这样设计的,它现在能够监听 activity 和 fragment。
3、LeakCanary 源码剖析
先看看 install 办法:
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
install 办法回来 RefWatcher 目标,这是一个链式调用,咱们一步步地来看。
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
refWatcher 办法回来 AndroidRefWatcherBuilder 目标,从姓名可知它是一个结构器,builder,它的结构函数也很简单,保存context
AndroidRefWatcherBuilder(@NonNull Context context) {
this.context = context.getApplicationContext();
}
持续回到 install 办法,检查 listenerServiceClass 办法:
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
AndroidRefWatcherBuilder 承继自 RefWatcherBuilder ,上面的代码便是为 AndroidRefWatcherBuilder 赋值一个成员变量,heapDumpListener 。持续回到 install 办法
excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
这句话的意思是,扫除各种现已的不是内存走漏的情况。其实为什么要在第一步中指出,这是一个 Builder呢,咱们常见的 builder 代码里,有各式各样的赋值,但最要害的代码往往仅仅它的 build 办法,咱们别被这种链式调用弄晕了,这些不重要,别死抠细节不放,大约理解意思就行,接下来咱们看最重要的办法:
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//调用build办法,构建 RefWatcher
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
if (watchActivities) {
//监听activity
ActivityRefWatcher.install(context, refWatcher);
}
//监听 fragment
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
build 办法回来 RefWatcher ,比较简单,其实便是将之前链式调用赋值的各个目标,赋值给 RefWatcher
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
if (heapDumpBuilder.excludedRefs == null) {
heapDumpBuilder.excludedRefs(defaultExcludedRefs());
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
最最要害的还得是 install 办法,本文中咱们只剖析 activity 的逻辑,fragment 暂不剖析。
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
install 办法中只有三行,第一步,获取 Application 目标,第二步,生成一个 ActivityRefWatcher 目标,第三步,看办法名,貌似是注册了一个监听,一个activity生命周期的监听。看看 lifecycleCallbacks 这个回调目标:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
要害代码终于出现,当activity 调用 onDestroyed 办法时,会调用lifecycleCallbacks 中的办法,然后能够拿到 activity 的引证。结合之前 Reference 的剖析,要监听目标是否被收回,首要得拿到它的引证,现在 activity 的引证拿到了。持续往下剖析。
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
经过拿到的 activity 引证,结构 KeyedWeakReference 目标,其实它承继自 WeakReference ,它是一个弱引证。构建弱引证的一起,在结构函数中增加 ReferenceQueue,当 activity 被收回时,KeyedWeakReference 目标会被增加到ReferenceQueue当中。假如出现内存走漏,则 ReferenceQueue 找不到对应的 KeyedWeakReference 目标,那么就能够判别产生内存走漏了。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
ensureGoneAsync办法中,经过 watchExecutor 履行一个 run 办法,watchExecutor 只会在主线程中履行它,持续检查 ensureGone办法
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;
}
//gone办法便是检查到 activity 现已被收回,则回来 DONE,标明内存无漏泄
if (gone(reference)) {
return DONE;
}
//调用 gc
gcTrigger.runGc();
removeWeaklyReachableReferences();
//调用gc之后,再次检查 ,假如 activity 还没被收回,则是有内存走漏了
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// 剖析 heap 的prof文件,找出内存走漏点
heapdumpListener.analyze(heapDump);
}
return DONE;
}
假如 activity 现已被收回,那么 ReferenceQueue 中将增加指向它的 Reference,这是一条大原则,一定要记住。在 watch 办法时,为每个 activity结构对应的Reference时,还增加了一个key,并把key增加到一个set当中。
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);
}
}
遍历 ReferenceQueue 中得到的Reference,拿到 Reference,一起删除 set 中对应的key。所以,set中不包括某个 key,则阐明对应的 activity现已被收回,反之则是没有收回。
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
gone办法正好验证这点,假如gone回来为true,那么整个进程也结束了。假如gone回来为false,则标明可能有内存走漏,所以履行一次gc之后 ,再次调用gone办法,检查是否有无走漏。假如还有走漏,则剖析生成的prof文件,找出要害的走漏途径。关于如何剖析 prof 文件,此处不展开了,其实 LeakCanary 也是借用其它的开源库来剖析的。
最后总结下整个进程:
在一个Activity履行完onDestroy()之后,将它放入WeakReference中,然后将这个WeakReference类型的Activity目标与ReferenceQueque关联。这时再从ReferenceQueque中检查是否有没有该目标,假如没有,履行gc,再次检查,还是没有的话则判别产生内存泄露了。最后用HAHA这个开源库去剖析dump之后的heap内存。