leakCanary
square 公司 提供的一款开源的内存泄漏检查工具,在程序中检测activity 是否被gc 回收
使用
-
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5' releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
-
在Application / activity 中初始化
-
protected void onCreate(Bundle savedInstanceState) { //.......调用install LeakCanary.install(getApplication()); }
-
原理分析
- 首先实现监听,通过
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
注册activity的事件监听,并在onDestroy
执行时,获取当前activity
- 判断是否会产生内存泄漏
- LeakCanary 是通过通过
WeakReference
+ReferenceQueue
来判断对象是否被系统GC回收
- 构建
WeakReference
时传递一个ReferenceQueue
- 如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中,如果垃圾回收后一直不加入到引用队列则可能产生内存泄漏
- 整体流程
- 监听activitiy的声明周期,执行Destroy后,当主线程空闲时,延迟5秒执行一次判断,判断当前activity是否被GC 回收,如果已经回收了,说明没有内存泄漏,如果还没回收,我们进一步确认,手动触发一下gc,然后再判断有没有回收,如果这次还没回收,说明Activity已经泄漏,此时将分析结果反馈出来
源码分析
install 入口
-
LeakCanary.install
-
public static RefWatcher install(Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall(); }
build()
模式构建了一个RefWatcher
- 其中
listenerServiceClass
方法绑定一个后台服务DisplayLeakService
以便将泄漏分析结果反馈,也可以重写这个类,进行一些上传日志等操作 excludedRefs
,设置白名单,发生泄漏时不提示
- 其中
- 下面看
buildAndInstall()
这里面创建了RefWatcher对象,通过RefWatcher创建ActivityRefWatcher,用来监听Activity是否被释放‘
构建RefWatcher对象 & ActivityRefWatcher
-
buildAndInstall
-
public RefWatcher buildAndInstall() { //创建Refwatcher 实例 RefWatcher refWatcher = build(); if (refWatcher != DISABLED) { //开启LeakCanary的应用,显示其图标. LeakCanary.enableDisplayLeakActivity(context); // 调用AcivityRefWatcher.installOnIcsPlus 完成ActivityRefWatcher的创建以及监听 ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher); } return refWatcher; }
1 构建RefWatcher对象
-
调用build()构建一个RefWatcher对象 该方法内部主要配置了如下参数来构建RefWatcher对象
-
watchExecutor
: 线程控制器,在 onDestroy()之后并且主线程空闲时执行内存泄漏检测debuggerControl
: 判断是否处于调试模式,调试模式中不会进行内存泄漏检测gcTrigger
: 用于GCwatchExecutor
首次检测到可能的内存泄漏,会主动进行GC,GC之后会再检测一次,仍然泄漏的判定为内存泄漏,进行后续操作heapDumper
: dump内存泄漏处的heap信息,写入hprof文件heapDumpListener
: 解析完hprof文件并通知DisplayLeakService弹出提醒excludedRefs
: 排除可以忽略的泄漏路径
-
2 构建ActivityRefWatcher
-
通过
ActivityRefWatcher.installOnIcsPlus
创建ActivityRefWatcher
以便完成对activity 的监听 -
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) { //创建ActivityRefWatcher ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); //开始观察activity 并在此 实现与activity声明周期绑定 activityRefWatcher.watchActivities(); }
3 完成生命周期绑定监听
-
watchActivities
-
public void watchActivities() { // 为了确保没有观察多次 要将之前绑定的声明周期回调解除绑定 stopWatchingActivities(); // 绑定声明周期 application.registerActivityLifecycleCallbacks(lifecycleCallbacks); } //解除绑定 public void stopWatchingActivities() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); }
-
通过
Application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
来绑定activity的生命周期 -
registerActivityLifecycleCallbacks
-
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) { // 绑定 destroy 声明周期 当activity 执行destroy时 将该destroy加入一个缓存队列 ActivityRefWatcher.this.onActivityDestroyed(activity); } };
-
可以看到上面只实现了对
activity的destroy
方法的监听,当监听到destroy事件之后就要分析当前activity是否被销毁了
-
4 判断是否被GC回收
-
通过
ActivityRefWatcher.this.onActivityDestroyed(activity);
方法将activity传递 -
void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); } 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(); // 生成key String key = UUID.randomUUID().toString(); //添加到retainedKeys set集合 retainedKeys.add(key); // 将activity 和 引用队列 key 关联起来包装成一个弱引用 // 弱引用和引用队列ReferenceQueue联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); //开始检测对应的activity 第一个参数为开始时间 ensureGoneAsync(watchStartNanoTime, reference); }
- retainedKeys 存贮了所有销毁的activity
5 开启线程异步执行判断
-
ensureGoneAsync
-
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { // 通过watchExecutor执行异步任务了 watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { //判断是否被回收 return ensureGone(reference, watchStartNanoTime); } });}
-
通过
watchExecutor
来执行异步任务,这里的watchExecutor
是个接口,实现类是AndroidWatchExecutor
-
AndroidWatchExecutor
- 官方注释,此执行器等待主线程空闲,然后以延迟的方式向串行后台线程发送POST
- 内部维护两个handler
mainHandler
主线程handlerbackgroundHandler
后台线程handler
-
execute
-
@Override public void execute(Retryable retryable) { if (Looper.getMainLooper().getThread() == Thread.currentThread()) { waitForIdle(retryable, 0); } else { postWaitForIdle(retryable, 0); } } //postWaitForIdleprivate void postWaitForIdle(final Retryable retryable, final int failedAttempts) { mainHandler.post(new Runnable() { @Override public void run() { waitForIdle(retryable, failedAttempts); } }); }
-
由上面代码可知无论当前是否主线程,都需要到主线程中执行
waitForIdle(retryable, failedAttempts);
-
void waitForIdle(final Retryable retryable, final int failedAttempts) { //这需要从主线程调用。 Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { postToBackgroundWithDelay(retryable, failedAttempts); return false; } }); }
- 设置IdleHandler()就是当主线程空闲的时候,如果设置了这个东西,就会执行它的queueIdle()方法
-
postToBackgroundWithDelay
-
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() { Retryable.Result result = retryable.run(); if (result == RETRY) { postWaitForIdle(retryable, failedAttempts + 1); } } }, delayMillis); }
-
延时5秒执行
retryable
的run()
,这里是通过backgroundHandler的post
执行,所以run
是在子线程执行的。这里的retryable
就是前面execute
传过来的:
-
-
6.判断过程
-
由上可知最终用是执行到了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; } //第二处代码 判断activity 是否被回收 if (gone(reference)) { return DONE; } // 第三处 若没回收则调用gc gcTrigger.runGc(); //第四处 -》再次执行第一处代码,移除已经回收掉的对象 removeWeaklyReachableReferences(); // 第五处 -》再次判断 对象是否被回收 若没回收则 开启DumpHeap并分析找到内存泄漏的地方返回 if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); // 第六处,生成heapDumpFile 并分析 File heapDumpFile = heapDumper.dumpHeap(); 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; }
-
观察第一处代码
-
removeWeaklyReachableReferences
-
private void removeWeaklyReachableReferences() { KeyedWeakReference ref; while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); }}
- 上面我们介绍过,当弱引用对象被回收则会放到关联的引用队列中,在第四步判断是否被GC回收的代码里面我们知道
retainedKeys
这个set集合,存储了所有销毁的activity对象 - 在这取出引用队列中所有的
Reference
,用他们的key
和retainedkeys
中的key
做匹配若匹配到则说明该activity已经被回收了就从retainedKeys
中移除该key
- 上面我们介绍过,当弱引用对象被回收则会放到关联的引用队列中,在第四步判断是否被GC回收的代码里面我们知道
-
-
第二处代码
-
gone(reference)
-
private boolean gone(KeyedWeakReference reference) { return !retainedKeys.contains(reference.key);}
-
将传递进来的
reference
的key
和清理之后的retainedKeys
匹配,若匹配到则说明该reference
持有的activity没有被回收,若没有回收则执行第三处 手动调用GC
-
-
第三处代码 调用GC 等待100 毫秒
- 手动调用Gc
- 等待100毫秒之后
-
到了第四处,再次执行第一处代码执行过后
-
到了第五处,再次执行第二处代码,此时若还没有被回收则发生内存泄漏
-
执行第六处代码 生成
heapDumpFile
分析内存泄漏的地方
源码分析总结
- 首先创建RefWatcher和ActivityRefWatcher
- 完成生命周期绑定
- 在Activity destroy时,通过watcher.execute 开启了一个闲时等待任务,延迟5秒来分析当前销毁的activity是否被释放,
- 首先检测引用队列,结合
retainedKeys
清空已经回收的对象 - 判断传递进来的对象是否被回收 调用gone方法 判断retainedKeys中是否存在传递进来的对象对应的key
- 若不存在说明回收了,返回DONE
- 若存在说明没回收,则手动触发GC
- 等待100毫秒之后,再次检测引用队列清空已经回收的对象
- 再次判断传递进来的对象是否被回收
- 若被回收则返回done
- 若没被回收则说明泄漏了, 生成
heapDumpFile
,调用heapdumpListener.analyze
分析内存泄漏产生的原因和位置
- 首先检测引用队列,结合
分析内存泄漏过程
上文中知道,当在retainedkeys中发现,未被回收的对象则床车工*.hprof文件并执行分析代码
如下
if (!gone(reference)) { // 第一处 若发下未被回收的对象,则将堆信息保存到文件 File heapDumpFile = heapDumper.dumpHeap(); //第二处 调用HeapDuamListener.analyze 来执行分析 // 生成HeapDump对象传入当前堆栈信息文件和refreences的key ,name,和被忽略的内存泄漏的白名单 heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); }
生成堆栈信息文件
- 调用
heapDumper.dumpHeap();
将堆信息保存到文件中
开始分析
生成HeapDump对象
-
生成HeapDump对象,根据堆信息文件,传递的判断对象的key,name, 以及忽略产生内存泄漏的白名单
-
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)
-
调用analyze方法分析
-
调用analyze方法传递HeapDump对象进行分析
-
heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
-
heapdumpListener 是个接口
-
/**接收要分析的堆转成的文件 */public interface Listener { Listener NONE = new Listener() { @Override public void analyze(HeapDump heapDump) { } }; void analyze(HeapDump heapDump);}
-
该接口的实现类是
ServiceHeapDumpListener
所以analyze的方式实现如下 -
@Override public void analyze(HeapDump heapDump) { checkNotNull(heapDump, "heapDump"); HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);}
-
可以看到是通过
HeapAnalyzerService
的runAnalysis
执行的分析HeapAnalyzerService
是一个IntentServes
,在单独的进程中运行,避免内存不足
-
runAnalysis
方法 开启新进程分析 内存泄漏 -
public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) { Intent intent = new Intent(context, HeapAnalyzerService.class); intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName()); intent.putExtra(HEAPDUMP_EXTRA, heapDump); // 启动当前Serves执行当前Service中重写的onHandleIntent context.startService(intent);}
-
onHandleIntent
-
@Override protected void onHandleIntent(Intent intent) { String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA); //获取堆文件信息 HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA); //创建HeapAnalyzer对象并注册内存泄漏白名单 HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs); // 调用CheckForLeak 分析内存泄漏 AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey); //发送分析结果 AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);}
HeapAnalyzer
严重内存泄漏的真实性HeapAnalyzer.checkForLeak
是真正分析内存泄漏的入口
-
-
分析DumpFile
-
由上面最后一步可知,通过
checkForLeak
分析内存泄漏 -
//在堆文件中搜索具有传入对象的Key的对应WeakReference实例,然后计算从该实例到GC根的最短强引用路径public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) { long analysisStartNanoTime = System.nanoTime(); try { HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); HprofParser parser = new HprofParser(buffer); //1.将堆文件转换为Snapshot Snapshot snapshot = parser.parse(); //2.去除重复的内存泄漏 deduplicateGcRoots(snapshot); //3 查找对应key的 泄漏 Instance leakingRef = findLeakingReference(referenceKey, snapshot); // 为空说明被释放了 if (leakingRef == null) { return noLeak(since(analysisStartNanoTime)); } //4 不为空则 找到泄漏对象最短引用路径路径并返回 return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); } catch (Throwable e) { return failure(e, since(analysisStartNanoTime)); } }
findLeakingReference
方法- 根据传递的key 去snapshot中匹配 若有匹配的,则说明发生内存泄漏
- 若没有匹配到 则说明已经回收了
findLeakTrace
- 获取泄漏对象的GcRoot 引用链
分析总结
- 发生内存泄漏首先将堆转文件(*.hprof)
- 创建HeapDump 对象,调用anayls开启新的进程分析对文件
- 设置泄漏白名单
- 将堆文件转为snapshot
- 用传递的对象的key去snapshot中去查找受否存在具有相同的key的对象
- 若有则说明对象没有被回收,产生内存泄漏,查找到最短的引用链并返回
- 若没有则说明对象被回收,不产生内存泄漏