LeakCanary是一个检测内存泄漏的工具,使用非常简单。主要用来检测Activity和Fragment内存泄漏,如果发生内存泄漏,直接在用UI显示哪里发生了泄漏并展示对象引用链。
LeakCanary地址:https://github.com/square/leakcanary
LeakCanary的使用
在gradle文件中加入依赖
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// 如果使用了 support fragment,需要依赖
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
在Application中加入
public class MyApplication extends Application {
private RefWatcher refWatcher = null;
@Override
public void onCreate() {
super.onCreate();
if (!LeakCanary.isInAnalyzerProcess(this)) { // 是否在主进程
refWatcher = LeakCanary.install(this);
}
}
}
如果想监视其他的Object是否内存泄漏了
refWatcher.watch(Object object)
原理
在LeakCanary 的 wiki 就简述了基本的原理:
- RefWatcher.watch() 为监视的对象创建一个 KeyedWeakReference;
- 然后,后台线程会检查引用是否被清除回收了,如果没有就触发GC;
- 如果引用还没被清除,则堆内存dump到文件系统,成一个.hprof文件
- HeapAnalyzerService运行在另外一个进程,其中内部的HeapAnalyzer 使用HAHA库解析heap dump
- 根据reference key,HeapAnalyzer 找到对应的 KeyedWeakReference,定位内存泄露
- HeapAnalyzer 可以找出 GC roots 的最短强引用路径,并确定是否是泄露。如果泄漏,建立导致泄露的引用链。
- 将泄漏结果(引用链)传给App进程DisplayLeakService,通知展示
我们主要看的就是LeakCanary.install(this)
到底发生了什么?
在解析流程之前先看下涉及到的几个重要的类:
RefWatcher: 核心类,负责管理和提供入口watch(),由AndroidRefWatcherBuilder创建RefWatcher,建造者模式
WatchExecutor: 负责控制执行检测内存泄漏任务
DebuggerControl:判断是否处于debug
GcTrigger:负责触发一次GC
HeapDump:表示指定时刻的堆栈的快照,AndroidHeapDump为子类
HeapDump.Builder: 负责创建HeapDump
HeapDump.Listener:监听器,当发生内存泄漏的时候,会收到消息,需要触发分析AndroidHeapDump任务
ServiceHeapDumpListener:HeapDump.Listener的实现类,当触发分析任务,调用HeapAnalyzerService执行分析任务
HeapAnalyzerService:是一个Android中四大组件之一的Service,运行在独立的进程,负责执行分析任务和UI通知
HeapAnalyzer:在HeapAnalyzerService内部中,是对DumpHeap分析内存泄漏和找出引用链的工具
retainKeys: 是一个Set<String>,保存着当前还没被回收的Reference的key
ReferenceQueue:引用队列,WeakReference可以关联引用队列,当reference被回收时,会被加入到ReferenceQueue,这样我们就可以判断哪些对象没有被回收了
DisplayLeakService:记录泄漏日志和展示通知的Service
其实,leakCanary的基本原理就是利用ReferenceQueue,在Activity销毁的时候判断对象有没有被加入ReferenceQueue,若没有则说明Activity还在存活,可能存在泄漏。
GC Root的种类
java 使用引用链法来判断一个引用是否该被回收,而出发点就是GC Root
- 虚拟机栈的对象引用
- 本地方法栈的对象引用
- 方法区的常量引用
- 方法区的静态对象引用
源码分析
isInAnalyzerProcess()
方法是用来判断当前的进程是否是在分析进程,因为多进程中,每个进程都会创建Application,而我们需要在非LeakCanary的进程中检测。
// LeakCanary
public static boolean isInAnalyzerProcess(@NonNull Context context) {
Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
// This only needs to be computed once per process.
if (isInAnalyzerProcess == null) {
isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
}
return isInAnalyzerProcess;
}
install方法是LeakCanary的总入口
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
这里使用了Build建造者模式,利用AndroidRefWatcherBuilder创建RefWatcher
我们分成几个部分:
- refWatch(application) 创建AndroidRefWatcherBuilder
- listenerServiceClass(DisplayLeakService.class) 设置监听内存泄漏和分析结果的 Service
- excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) 设置忽略的内存泄漏的错误
- buildAndInstall() 初始化AndroidRefWatcher
1. refWatch(application)
// LeakCanary
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
这里只是创建了AndroidRefWatcherBuilder,最后使用Build模式创建AndroidRefWatcher
2.listenerServiceClass(DisplayLeakService.class)
// AndroidRefWatcherBuilder
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
创建了heapDumpListener负责heapDump的分析和处理
isAssignableFrom() 是object方法,
例如 a.isAssignableFrom(b) 判断a是不是b的父类或接口
如果listenerServiceClass继承或者是DisplayLeakService,则enableDisplayLeakActiviy为true,表示显示LeakCanary界面
// ServiceHeapDumpListener
public final class ServiceHeapDumpListener implements HeapDump.Listener {
private final Context context;
private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;
public ServiceHeapDumpListener(@NonNull final Context context,
@NonNull final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
这里简单看一下ServiceHeapDumpListener ,主要方法在analyze()
,实际上也只是通知了HeapAnalyzerService.runAnalysis()去分析heapDump;
HeapAnalyzerService是运行在独立进程的Service。它是一个IntentService,执行完一次任务就会终止。
3. excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
AndroidExcludedRefs.java类中维护着一系列的特定的内存泄漏类型,在执行内存泄漏和显示的时候也会忽略这些类
4. buildAndInstall()
// AndroidRefWatcherBuilder
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
这里也主要分成四个部分:
(1). build() 构建初始化RefWatcher
(2). LeakCancaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true) 设置DisplayLeakActivity不显示图标
(3). ActivityRefWatcher.install(context, refWatcher) 监测Activity
(4). FragmentRefWatcher.Helper.install(context, refWatcher) 监测Fragment
(1) build()
// RefWatcherBuilder
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
...
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
AndroidRefWatcherBuilder 是 RefWatcherBuilder的子类,build()方法内部负责创建默认的组件。AndroidRefWatcherBuilder 重写了部分默认方法。
// AndroidRefWatcherBuilder
@Override protected @NonNull HeapDumper defaultHeapDumper() {
LeakDirectoryProvider leakDirectoryProvider =
LeakCanaryInternals.getLeakDirectoryProvider(context);
// 创建默认的 AndroidHeapDumper
return new AndroidHeapDumper(context, leakDirectoryProvider);
}
@Override protected @NonNull DebuggerControl defaultDebuggerControl() {
// 创建默认的 AndroidDebuggerControl
return new AndroidDebuggerControl();
}
@Override protected @NonNull HeapDump.Listener defaultHeapDumpListener() {
// 创建默认的 ServiceHeapDumpListener
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
@Override protected @NonNull WatchExecutor defaultWatchExecutor() {
// 创建默认的 AndroidWatchExecutor
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
@Override protected @NonNull
List<Class<? extends Reachability.Inspector>> defaultReachabilityInspectorClasses() {
// 创建默认的 AndroidReachabilityInspectors
return AndroidReachabilityInspectors.defaultAndroidInspectors();
}
(2) LeakCancaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true)
// LeakCanaryInternals
public static void setEnabledAsync(Context context, final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
这里使用了AsyncTask内部的并发线程池 setEnabledBlocking()
// LeakCanaryInternals
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;
// 设置是否隐藏应用图标
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
如果设置一个app的mainActivity为COMPONENT_ENABLED_STATE_DISABLED状态,则不会再launcher的程序图标中发现该app。
这里设置了DisplayLeakActivity在Launcher程序图标不出现
(3). ActivityRefWatcher.install(context, refWatcher)
// ActivityRefWatcher
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) {
refWatcher.watch(activity);
}
};
这里才是LeakCanary监测Activity的核心,利用ActivityLifecycleCallbacks监听Activity的生命周期,在Activity销毁的时候,onActivityDestroyed调用refWatcher.watch()
refWatcher.watch()执行了实际的核心工作,暂时先跳过,后续再继续分析refWatcher.watch()
(4). FragmentRefWatcher.Helper.install(context, refWatcher)
// FragmentRefWatcher
final class Helper {
private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =
"com.squareup.leakcanary.internal.SupportFragmentRefWatcher";
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
if (SDK_INT >= O) { // 如果大于Anroid 26,需要增加AndroidOFragmentRefWatcher
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
// 利用反射添加SupportFragmentRefWatcher
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);
}
以上部分是添加了FragmentRefWatcher,用来监测Fragment,其中SupportFragmentRefWatcher是在leakcanary-support-fragment包通过反射添加进来,需要在build.gradle添加依赖。
// AndroidOFragmentRefWatcher
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);
}
};
@Override public void watchFragments(Activity activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
监测Fragment和监测Activity基本是一样的,利用了FragmentLifecycleCallbacks监听Fragment的生命周期,在销毁状态onFragmentViewDestroyed和onFragmentDestroyed分别检测View和Fragment。
refWatcher.watch(Object)
无论是检测Activity还是Fragment,或者是其他类型,都是调用refWatcher.watch(Object)
再看下AnroidRefWatcher重要的组成部分:
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
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);//1
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);//2
ensureGoneAsync(watchStartNanoTime, reference);//3
}
watch()主要的工作:
- 为检测的Object创建一个key,并添加进retainedKeys表示Objectha还存活
- 为Object创建一个弱应用,关联queue为引用队列
- ensureGoneAsync()执行监测任务
// RefWatcher
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
watchExecutor 是AndroidWatchExecutor
//AndroidWatchExecutor
public void execute(@NonNull Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
// 在主线程
waitForIdle(retryable, 0);
} else {
// 切换到主线程
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
// 利用Handler切换到主线程
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// 添加任务到主线程Looper,等待Looper空闲时候执行
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
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;
// backgroudhandler所在的是HandlerThread
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
这里利用Looper的IdleHandler,在Looper空闲的时候执行任务。
简单总结下AndroidWatcherExecutor,等待主线程空闲的时候,在子线程执行ensureGone()
// RefWatcher
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 1. 从retainedKeys移除掉已经被会回收的弱引用的key
removeWeaklyReachableReferences();
// 2. 如果是debug模式,会继续重试
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 3. 若当前引用不在retainedKeys,说明不存在内存泄漏
if (gone(reference)) {
return DONE;
}
// 4. 触发一次gc
gcTrigger.runGc();
// 5.再次从retainedKeys移除掉已经被会回收的弱引用的key
removeWeaklyReachableReferences();
if (!gone(reference)) {
// 存在内存泄漏
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 6. 创建heapDump文件,还没写入
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.调用heapdumpListener分析
heapdumpListener.analyze(heapDump);
}
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
上面的注释把流程基本阐述清楚了,基本逻辑是
- 利用引用队列移除掉已经回收的对象
- 可能因为回收不及时,所以再次gc
- 若对象还没被回收,则发生内存泄漏
- 创建HeapDump,后台service执行分析任务
最后看HeapAnalyzerService分析HeapDump
// HeapAnalyzerService
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabledBlocking(context, HeapAnalyzerService.class, true);
setEnabledBlocking(context, listenerServiceClass, true);
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
ContextCompat.startForegroundService(context, intent);
}
这里的listenerServiceClass 就是从最上面传递过来的DisplayLeakService.class,最后他负责记录日志和展示通知。
runAnalysis()
启动了HeapAnalyzerService,它是一个IntentService运行在独立进程,负责分析HeapDump和显示通知,既然是IntentService,直接看onHandleIntent(),内部又调用了onHandleIntentInForeground()
protected void onHandleIntentInForeground(@Nullable Intent intent) {
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA); // DisplayLeakService.class
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
// 创建HeapAnalyzer
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
// HeapAnanlyzer工具分析
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 启动DisplayLeakService记录日志和展示通知
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
HeapAnalyzer 可以找出 GC roots 的最短强引用路径,并确定是否是泄露。如果泄漏,建立导致泄露的引用链。
// HeapAnalyzer
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
@NonNull String referenceKey,
boolean computeRetainedSize) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
//将heap文件封装成MemoryMappedFileBuffer
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
//创建hprof解析器,解析hprof文件
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
// 移除相同GC root
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
// 找出内存泄漏对象
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
//检测是否存在泄漏的引用
if (leakingRef == null) {
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
//根据leakingRef寻找引用路径
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
再往下就是如何分析HeapDump,不是本文的重点。
总结:
- 无论你是监测Activity还是Fragment亦或是其他Object,最后都是RefWatcher.watch(object)
- RefWatcher添加监测的引用, 在主线程 idle 后进行一次强制 gc 后再判断该引用是否在引用队列中,否则就可能是内存泄漏
- RefWatcher 通过 HeapDumper 创建HeapDump 和 hprof 文件
- HeapAnalyzerService的HeapAnalyzer 分析内存泄漏和找出引用链,最后DisplayLeakService通知