源码分析 - LeakCanary
1、使用
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'
}
public class MyApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher = setRefWatcher();
}
private RefWatcher setRefWatcher(){
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}
public static RefWatcher getRefWathcher(Context context){
MyApplication myApplication = (MyApplication) context.getApplicationContext();
return myApplication.refWatcher;
}
}
这样使用只会检测Activity和标准Fragment是否发生内存泄漏,如果要检测V4包的Fragment在执行完onDestroy()之后是否发生内存泄露的话,则需要在Fragment的onDestroy()方法中加上如下两行代码去监视当前的Fragment:
RefWatcher refWatcher = WanAndroidApp.getRefWatcher(_mActivity);
refWatcher.watch(this);
源码分析
流程:
- 监听哪些类的堆内存泄露情况
- 如何找到泄露的对象或类
- 找到泄露后如何找到导致泄露的引用链
- 找到引用链后将引用链抛出来写入到泄露日志中
首先我们从LeakCanary.install()开始
LeakCanary.install():
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
-
refWatcher(application) : 创建一个AndroidRefWatcherBuilder
-
listenerServiceClass(DisplayLeakService.class) : 传入了一个HeapDump.Listener,HeapDump.Listener中的一个参数是DisplayLeakService,DisplayLeakService的作用是展示泄露分析的结果日志,然后会展示一个用于跳转到显示泄露界面DisplayLeakActivity的通知。
-
excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) :
过滤那些系统或者制造商导致的泄露,这些泄露是我们开发者无法处理的泄露 -
buildAndInstall() : 创建一个RefWatcher,并且开始观察Activity和Fragment。
注意,这里如果在一个进程调用多次会抛出UnsupportedOperationException异常
AndroidExcludedRefs 类
它是一个enum类,它声明了Android SDK和厂商定制的SDK中存在的内存泄露的case,根据AndroidExcludedRefs这个类的类名就可看出这些case都会被Leakcanary的监测过滤掉。目前这个版本是有46种这样的case被包含在内,后续可能会一直增加。
这里我们来说一下如何观察Activity和Fragment的:
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
我们看到,分别调用了ActivityRefWatcher.install(context, refWatcher);和FragmentRefWatcher.Helper.install(context, refWatcher);
我们先来看Activity:
public final class ActivityRefWatcher {
//...
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);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
}
这里可以看到,ActivityRefWatcher类中的install方法中创建了一个ActivityRefWatcher,并且注册了它的Lifecycle,在onActivityDestroyed时调用了refWatcher.watch(activity);来进行观察
Fragment 的观察基本一致:
public interface FragmentRefWatcher {
final class Helper {
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
Constructor<?> constructor =
fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
FragmentRefWatcher supportFragmentRefWatcher =
(FragmentRefWatcher) constructor.newInstance(refWatcher);
fragmentRefWatchers.add(supportFragmentRefWatcher);
} catch (Exception ignored) {
}
if (fragmentRefWatchers.size() == 0) {
return;
}
Helper helper = new Helper(fragmentRefWatchers);
Application application = (Application) context.getApplicationContext();
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
//...
}
}
这里面的逻辑很简单,首先在将Android标准的Fragment的RefWatcher类,即AndroidOfFragmentRefWatcher添加到新创建的fragmentRefWatchers中。在使用反射将leakcanary-support-fragment包下面的SupportFragmentRefWatcher添加进来,如果你在app的build.gradle下没有添加下面这行引用的话,则会拿不到此类,即LeakCanary只会检测Activity和标准Fragment这两种情况。
watcher.watchFragments(activity);最终调用到了:
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManagerfm, Fragment fragment) {
refWatcher.watch(fragment);
}
可以看到,不管是activity还是fragment,最后都调用到了watch中,我们来看下watch(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);
}
- 为每个reference创建一个唯一的UUID,把这个UUID放入到set集合retainedKeys中。
- 为watchedReference对象创建一个弱引用KeyedWeakReference
- 调用ensureGoneAsync(watchStartNanoTime, reference);
我们先看看KeyedWeakReference中做了什么:
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
//1、
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
在super中,将referent注册到queue中,这里说一下,在我们创建软引用、弱引用时,可以声明一个ReferenceQueue:
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
在KeyedWeakReference内部,使用了key和name标识了一个被检测的WeakReference对象。在注释1处,将弱引用和引用队列 ReferenceQueue 关联起来,如果弱引用reference持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列referenceQueue中。即 KeyedWeakReference 持有的 Activity 对象如果被GC回收,该对象就会加入到引用队列 referenceQueue 中。
如果我们在创建一个引用对象时,指定了ReferenceQueue,那么当引用对象指向的对象达到合适的状态(根据引用类型不同而不同)时,GC 会把引用对象本身添加到这个队列中,方便我们处理它,因为“引用对象指向的对象 GC 会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己清理。”
参考:Reference 、ReferenceQueue 详解
再来看看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);
}
});
}
通过异步调用ensureGone(reference, watchStartNanoTime);,watchExecutor我们往回翻找可以发现其实是AndroidWatchExecutor,来看下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;
//1、
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(@NonNull Retryable retryable) {
//2、
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
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() {
//3、
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
//4、
Retryable.Result result = retryable.run();
if (result == RETRY) {
//5、
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}
- 1、initialDelayMillis默认为5s
- 2、如果当前是主线程,调用waitForIdle(retryable, 0);,如果不是,通过handler机制切换到主线程调用waitForIdle(retryable, failedAttempts);
- 3、queueIdle方法是在消息队列用完消息时调用,必须在主线程调用
- 4、在子线程中调用retryable.run(),run()方法调用到了RefWatcher类ensureGoneAsync方法中的run方法
- 5、如果是RETRY,那么递归调用postWaitForIdle,但是时间会加1s
接下来我们在看下ensureGone(reference, watchStartNanoTime)方法:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//1、
removeWeaklyReachableReferences();
//2、
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//3、
if (gone(reference)) {
return DONE;
}
//4、
gcTrigger.runGc();
//5、
removeWeaklyReachableReferences();
//6、
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//7、
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();
//8、
heapdumpListener.analyze(heapDump);
}
return DONE;
}
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);
}
}
- 1、调用removeWeaklyReachableReferences,queue.poll()是从ReferenceQueue中取出queue的第一个Reference,如果它是可达的,那么将它从queue中移除并且返回,如果有不可达的Reference(该被回收的),直接返回null。通过while循环queue.poll(),可以将queue中的所有可达对象找出来并且从queue中移除,再调用将其从retainedKeys中remove掉。如果有需要被回收的对象,则直接停止循环
- 2、如果当前是debug模式下,那么不执行回收策咯,因为在debug模式下可能存在假泄露
- 3、如果retainedKeys为空,那么表示所有对象均被回收,不存在泄露
- 4、执行gc
- 5、再次调用removeWeaklyReachableReferences
- 6、如果retainedKeys还不为空,那么说明存在不能被回收的弱引用对象,表示已经发生了泄露
- 7、生成一个heapDumpFile,并且创建一个Notification来通知用户
- 8、回调到ServiceHeapDumpListener的analyze方法中来分析泄露
来看下ServiceHeapDumpListener类:
public final class ServiceHeapDumpListener implements HeapDump.Listener {
...
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
可以看到,这里执行了HeapAnalyzerService的runAnalysis()方法,为了避免降低app进程的性能或占用内存,这里将HeapAnalyzerService设置在了一个独立的进程中。接着继续分析runAnalysis()方法里面的处理。
public final class HeapAnalyzerService extends ForegroundService
implements AnalyzerProgressListener {
...
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
...
ContextCompat.startForegroundService(context, intent);
}
...
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
...
// 1
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
// 2
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 3
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
...
}
这里的HeapAnalyzerService实质是一个类型为IntentService的ForegroundService,执行startForegroundService()之后,会回调onHandleIntentInForeground()方法。
- 1、首先会新建一个HeapAnalyzer对象,顾名思义,它就是根据RefWatcher生成的heap dumps信息来分析被怀疑的泄漏是否是真的。
- 2、调用它的checkForLeak()方法去使用haha库解析 hprof文件
- 3、通过sendResultToListener调用到onHandleIntentInForeground中的onHeapAnalyzed,最后调用到DisplayLeakService中的DisplayLeakActivity.createPendingIntent来开启一个泄露的Activity
到此,我们就分析完了整个过程,整理一下
- 1、Leakcanary.install中创建了一个RefWatcher
- 2、通过RefWatcher中的watch()来观察Activity和fragment,观察的方式是为被观察的activity或fragment创建一个WeakReference
- 3、将WeakReference放入到一个set集合retainedKeys中
- 4、调用检测方法,在异步线程中,先将retainedKeys中可达的全部移除
- 5、一旦发现不可达的WeakReference,执行gc
- 6、如果引用仍然没有被清除(retainedKeys不为空),那么它将会把堆栈信息保存在文件系统中的.hprof文件里。
- 7、HeapAnalyzerService被开启在一个独立的进程中,并且HeapAnalyzer使用了HAHA开源库解析了指定时刻的堆栈快照文件heap dump。
- 8、从heap dump中,HeapAnalyzer根据一个独特的引用key找到了KeyedWeakReference,并且定位了泄露的引用。
- 9、HeapAnalyzer为了确定是否有泄露,计算了到GC Roots的最短强引用路径,然后建立了导致泄露的链式引用。
- 10、这个结果被传回到app进程中的DisplayLeakService,然后一个泄露通知便展现出来了。