leakcanary是什么?
leakcanary是Square开源的一个内存泄露自动探测神器,它是一个Android和Java的内存泄露检测库,可以大幅度减少了开发中遇到的OOM问题。
下图为官方应用图标:
简介
Github地址:leakcanary
特点
- 使用简单
- 实时检测
- 及时的消息提醒及log日志
内存泄漏
本文主要介绍leakcanary的实现及原理。关于内存泄漏的定义及Android常见的内存泄漏及解决方法,推荐参考如下文章:
leakcanary怎么用?
1、gradle引入库
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
2、自定义Application并且在onCreate中进行初始化
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;
}
LeakCanary.install(this);
// Normal app init code...
}
}
leakcanary核心执行流程是怎样?
leakcanary主要是通过弱引用持有监听的实例对象,每个弱引用生成唯一的id,内部维护一个retainedKeys。通过触发gc,循环获取ReferenceQueue中的对象,若已被gc回收,则把id从keys中移除,最后keys维护了一个内存泄漏的实例集合。接着通过dump堆内存信息,用Square的haha库对hprof文件进行解析,最终获取内存泄漏实例的引用链,推送到前台通知。
关键类功能说明
类 | 说明 |
---|---|
LeakCanary | 外观模式,统一的注册类,install会返回RefWatcher对象,用于监听内存泄漏 |
RefWatcher | 用于整体调度监听对象的内存泄漏,是核心的类。内部维护了watchExecutor(异步分析线程池)、debuggerControl(用于判断是否debug模式)、gcTrigger(触发gc)、heapDumper(实现为AndroidHeapDumper,用于dump内存堆快照信息)、retainedKeys(用于记录内存泄漏的对象)、queue(弱引用被gc回收会加入到该队列);提供watch()方法对对象进行内存泄漏的判断分析。 |
ActivityRefWatcher | 用于监听Activity的生命周期、执行watch() |
FragmentRefWatcher | 用于监听Fragment的生命周期、执行watch() |
KeyedWeakReference | 将检测的对象转换为虚引用持有,便于监听GC的回收,内部维护了一个key属性,用作唯一的标识(UUID.randomUUID()生成) |
DebuggerControl | 实现为AndroidDebuggerControl,用于判断是否属于调试模式(Debug.isDebuggerConnected()) |
GcTrigger | 用于触发GC操作,(Runtime.getRuntime().gc()) |
HeapDump | 堆内存信息的包装类,包含heapDumpFile、excludedRefs、referenceKey等对象 |
ExcludedRefs | 用于排除一些已知的系统内存泄漏问题,避免再次提醒 |
HeapAnalyzerService | IntentService,用于开始执行内存泄漏的堆信息分析 |
HeapAnalyzer | 堆内存分析器,用于堆信息分析的整体调度 |
HprofParser | haha库中用于解析hprof文件 |
Snapshot | haha库中,堆信息内存快照 |
AnalysisResult | 分析结果包装类,包含className、leakTrace(泄漏的引用链)、retainedHeapSize(内存泄漏的大小)等 |
代码执行流程
leakcanary的核心流程主要包含5个步骤。
1、install-注册
2、bind-设置监听
3、watch-触发监听,分析泄漏
4、dump-堆内存信息
5、analyze-分析,获取引用链
这里先上一下整体的流程图,建议结合源码进行查看。
下面我们通过上诉5个步骤相关的源码来进行分析。
install
我们知道,使用leakcanary主要在application中进行注册,调用LeakCanary.install(this)。相关的实现代码如下:
public static RefWatcher install(Application application) {
//创建了RefWatcher(也就是检测内存泄漏的类)
return refWatcher(application)
//设置了内存泄漏的通知Service(通知)
.listenerServiceClass(DisplayLeakService.class)
//设置了需要忽略的已知的系统级别的内存泄漏(可以自定义)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
//开始监听
.buildAndInstall();
}
bind
leakcanary主要是监听应用的Activity、Fragment的内存泄漏。那么bind的这个环节,主要是通过监听Activity、Fragment相关的生命周期,当对象onDestry的时候,开始执行watch操作。
首先我们先看buildAndInstall()方法,实现如下:
1、buildAndInstall
public RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//初始化RefWatcher对象
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (watchActivities) {//默认true
//开始绑定Activity的监听
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {//默认true
//开始绑定Fragment的监听
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
2、ActivityRefWatcher.install(context, refWatcher)
public static void install(Context context, RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//通过Application的ActivityLifecycleCallbacks监听Activity的生命周期
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
//当Activity销毁的时候,开始触发watch进行分析
refWatcher.watch(activity);
}
};
3、 FragmentRefWatcher.Helper.install(context, refWatcher)
public static void install(Context context, RefWatcher refWatcher) {
//初始化FragmentRefWatcher对象及集合
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);
//首先开始监听Activity的生命周期
Application application = (Application) context.getApplicationContext();
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//当Activity创建的时候,进行Fragment的绑定监听
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
@Override public void watchFragments(Activity activity) {
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager =
((FragmentActivity) activity).getSupportFragmentManager();
//通过FragmentManager注册FragmentLifecycleCallbacks,监听Fragment的生命周期
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
//当Fragment销毁的时候,开始触发watch进行分析
refWatcher.watch(fragment);
}
};
watch
watch主要是通过弱引用持有监听的实例对象,每个弱引用生成唯一的id,内部维护一个retainedKeys。通过触发gc,循环获取ReferenceQueue中的对象,若已被gc回收,则把id从keys中移除,最后keys维护了一个内存泄漏的实例集合。触发下一步dump操作。关键的实现代码如下:
1、watch
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);
//生成虚引用
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//开始触发线程进行分析
ensureGoneAsync(watchStartNanoTime, reference);
}
2、ensureGone
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);
//刷新keys
removeWeaklyReachableReferences();
//判断是否在debug模式
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//如果已回收不用处理
if (gone(reference)) {
return DONE;
}
//没回收,触发gc,提高准确性
gcTrigger.runGc();
//再次刷新keys
removeWeaklyReachableReferences();
//如果keys中还是存在该虚引用,则判定为内存泄漏
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();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
//判断对象是否已被gc回收
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
//循环获取ReferenceQueue中的对象,若被gc回收,从keys中移除
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);
}
}
dump
在watch中的ensureGone方法中,当监听到内存泄漏的方法时,会开始执行dump,下载堆内存快照文件。具体的实现如下:
//如果keys中还是存在该虚引用,则判定为内存泄漏
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();
heapdumpListener.analyze(heapDump);
}
关键的heapDumper.dumpHeap()实现如下:
public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
//显示冻结弹层
showToast(waitingForToast);
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
Notification notification = LeakCanaryInternals.buildNotification(context, builder);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
int notificationId = (int) SystemClock.uptimeMillis();
notificationManager.notify(notificationId, notification);
Toast toast = waitingForToast.get();
try {
//关键的实现,dump堆内存信息
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
//隐藏冻结弹层
cancelToast(toast);
notificationManager.cancel(notificationId);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
analyze
当dump堆内存信息成功后,会执行如下方法heapdumpListener.analyze(heapDump),开始执行分析,具体的实现如下:
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
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);
}
通过源码可知,会开启一个IntentService进行分析的操作。接下来看,HeapAnalyzerService的onHandleIntentInForeground实现,如下:
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
//构建了堆解析器
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
//开始执行分析
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
接下来是使用了Square的开源库haha去分析hprof文件,获取泄漏对象的引用链,相关的实现如下:
public AnalysisResult checkForLeak(File heapDumpFile, 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);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef, boolean computeRetainedSize) {
listener.onProgressUpdate(FINDING_SHORTEST_PATH);
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {
return noLeak(since(analysisStartNanoTime));
}
listener.onProgressUpdate(BUILDING_LEAK_TRACE);
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
String className = leakingRef.getClassObj().getClassName();
long retainedSize;
if (computeRetainedSize) {
listener.onProgressUpdate(COMPUTING_DOMINATORS);
// Side effect: computes retained size.
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;
retainedSize = leakingInstance.getTotalRetainedSize();
// TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
}
} else {
retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
}
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
关于haha库的具体实现,可参考如下链接。
最后在onHandleIntentInForeground的方法最后,会执行AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);将最终分析的结果推送到前台的通知。这就完成了leakcanary对于内存泄漏的关键步骤分析。
leakcanary是如何进行内存泄漏的监听?
leakcanary的内存泄漏监听包含2个方面,一个是对Activity的监听,一个是对Fragment的监听。
1、 Activity内存泄漏监听
通过上述的流程分析,我们可知。leakcanary通过对 application注册ActivityLifecycleCallbacks的监听,当回调onDestroy的时候,开始对Activity进行watch操作
2、Fragment内存泄漏监听
通过上述的流程分析,我们可知。leakcanary通过对 application注册ActivityLifecycleCallbacks的监听,当回调onActivityCreate的时候,通过FragmentManager注册FragmentLifecycleCallbacks的监听,当回调onDestroy的时候,开始对Fragment进行watch操作
leakcanary是如何进行内存泄漏的分析?
leakcanary主要是通过监听Activity及Fragment的生命周期,当对象onDestroy的时候,使用弱引用持有该实例对象,每个弱引用生成唯一的id,内部维护一个retainedKeys。通过触发gc,循环获取ReferenceQueue中的对象,若已被gc回收,则把id从keys中移除,表示已正常被gc回收,最后keys维护了一个内存泄漏的集合。主要是通过弱引用和ReferenceQueue机制来进行内存泄漏的分析。
leakcanary是如何dump内存信息?
通过上述的dump流程,leakcanary主要是通过Android系统提供的api进行堆内存信息的保存。具体的api如下:
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
leakcanary是如何进行堆信息的分析?
leakcanary主要是使用haha库对hprof的文件进行解析,最后分析出内存泄漏对象的引用链,及泄漏的内存大小。关于haha库的详细使用,可参考:
总结
思考
Square真是一家牛逼的公司,而且热衷于对开源社区的贡献。在Android的生态中,贡献了诸如okhttp、picasso、leakcanary、javapoet、retrofit等主流的框架。
参考资料
关于
欢迎关注我的个人公众号
微信搜索:一码一浮生,或者搜索公众号ID:life2code