利用 LeakCanary 来检查 Android 内存泄漏

前言

你被概率性的 OOM 困扰么?有时候,OOM 像幽灵一样,挥之不去,可真想把它揪出来时,又捉之不着。或许,是时候用 LeakCanary 来诊断一下了。它是一个用来检查 Android 下内存泄漏的开源库,这篇文章主要介绍其用法、架构和其背后的实现原理。

Square 有篇文章介绍了开发这个库的原因。他们的一个付款流程里,需要用到用户的签名,他们直接用 Bitmap 来画签名,Bitmap 大小和屏幕分辨率是一样的。问题来了,在试图创建这个 Bitmap 对象时,概率性 OOM 如幽灵般相随。他们试了几个方法:

  • 使用 Bitmap.Config.ALPHA_8 来节省内存
  • 捕获 OutOfMemoryError 异常,调用 gc 清理内存,然后重试几次

最终这些都不起作用。最终他们发现他们在错误的方向上走得太远了。当存在内存泄漏时,可用内存越来越少,这个时候 OOM 可以发生在任何地方,特别是试图创建一些大内存对象,如 Bitmap 的时候。

我们在上一篇文章《Android 内存与性能》里介绍了使用 MAT 来分析内存泄漏的方法。概括起来核心步骤是:

  • 发生 OOM 或做一些可能存在内存泄漏的操作后,导出 HPROF 文件
  • 利用 MAT 结合代码分析,来发现一些引用异常,比如哪些对象本来应该被回收的,却还在系统堆中,那么它就是内存泄漏

如果有一个工具能自动完成这些事情,甚至在发生 OOM 之前,就把内存泄漏报告给你,那是多么美好的一件事情啊。LeakCanary 就是用来干这个事情的。在测试你的 App 时,如果发生了内存泄漏,状态栏上会有通知告诉你。logcat 上也会有相应的 log 通知你。

启发

LeakCanary 产生的背后有几个有意思的启发。一是像 Square 这样公司一样会被 OOM 这种问题困扰;二是他们也会犯错,试了几种方法都不起作用;三是他们最终用一个优雅的方式解决了这个问题,并且通过开源库的方式让所有人共享他们的工作成果。

用法

监控 Activity 泄露

我们经常把 Activity 当作为 Context 对象使用,在不同场合由各种对象引用 Activity。所以,Activity 泄漏是一个重要的需要检查的内存泄漏之一。

public class ExampleApplication extends Application {    public static RefWatcher getRefWatcher(Context context) {        ExampleApplication application = (ExampleApplication) context.getApplicationContext();        return application.refWatcher;    }    private RefWatcher refWatcher;    @Override public void onCreate() {        super.onCreate();        refWatcher = LeakCanary.install(this);    }}

LeakCanary.install() 返回一个配置好了的 RefWatcher 实例。它同时安装了 ActivityRefWatcher 来监控 Activity 泄漏。即当 Activity.onDestroy() 被调用之后,如果这个 Activity 没有被销毁,logcat 就会打印出如下信息告诉你内存泄漏发生了。

    * com.example.leakcanary.MainActivity has leaked:    * GC ROOT thread java.lang.Thread.<Java Local> (named ‘AsyncTask #1‘)    * references com.example.leakcanary.MainActivity$2.this$0 (anonymous class extends android.os.AsyncTask)    * leaks com.example.leakcanary.MainActivity instance    * Reference Key: c4d32914-618d-4caf-993b-4b835c255873    * Device: Genymotion generic Google Galaxy Nexus - 4.2.2 - API 17 - 720x1280 vbox86p    * Android Version: 4.2.2 API: 17    * Durations: watch=5100ms, gc=104ms, heap dump=82ms, analysis=3008ms

Notes

LeakCanary 自动检测 Activity 泄漏只支持 Android ICS 以上版本。因为 Application.registerActivityLifecycleCallbacks() 是在 API 14 引入的。如果要在 ICS 之前监测 Activity 泄漏,可以重载 Activity.onDestroy() 方法,然后在这个方法里调用 RefWatcher.watch(this) 来实现。

监控 Fragment 泄漏

public abstract class BaseFragment extends Fragment {    @Override     public void onDestroy() {        super.onDestroy();        RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());        refWatcher.watch(this);    }}

当 Fragment.onDestroy() 被调用之后,如果这个 fragment 实例没有被销毁,那么就会从 logcat 里看到相应的泄漏信息。

监控其他泄漏

    ...    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());    refWatcher.watch(someObjNeedGced);

当 someObjNeedGced 还在内存中时,就会在 logcat 里看到内存泄漏的提示。

集成 LeakCanary 库

dependencies {    debugCompile ‘com.squareup.leakcanary:leakcanary-android:1.3‘    releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.3‘}

在 debug 版本上,集成 LeakCanary 库,并执行内存泄漏监测,而在 release 版本上,集成一个无操作的 wrapper ,这样对程序性能就不会有影响。

原理

LeakCanary 流程图

LeakCanary 的机制如下:

  1. RefWatcher.watch() 会以监控对象来创建一个 KeyedWeakReference 弱引用对象
  2. 在 AndroidWatchExecutor 的后台线程里,来检查弱引用已经被清除了,如果没被清除,则执行一次 GC
  3. 如果弱引用对象仍然没有被清除,说明内存泄漏了,系统就导出 hprof 文件,保存在 app 的文件系统目录下
  4. HeapAnalyzerService 启动一个单独的进程,使用 HeapAnalyzer 来分析 hprof 文件。它使用另外一个开源库 HAHA。
  5. HeapAnalyzer 通过查找 KeyedWeakReference 弱引用对象来查找内在泄漏
  6. HeapAnalyzer 计算 KeyedWeakReference 所引用对象的最短强引用路径,来分析内存泄漏,并且构建出对象引用链出来。
  7. 内存泄漏信息送回给 DisplayLeakService,它是运行在 app 进程里的一个服务。然后在设备通知栏显示内存泄漏信息。

几个有意思的代码

如何导出 hprof 文件

File heapDumpFile = new File("heapdump.hprof");Debug.dumpHprofData(heapDumpFile.getAbsolutePath());

可以参阅 AndroidHeapDumper.java 的代码。

如何分析 hprof 文件

这是个比较大的话题,感兴趣的可以移步另外一个开源库 HAHA,它的祖先是 MAT。

如何使用 HandlerThread

可以参阅 AndroidWatchExecutor.java的代码,特别是关于 Handler, Loop 的使用。

怎么知道某个变量已经被 GC 回收

可以参阅 RefWatcher.java 的 ensureGone() 函数。最主要是利用 WeakReference 和 ReferenceQueue 机制。简单地讲,就是当弱引用 WeakReference 所引用的对象被回收后,这个 WeakReference 对象就会被添加到 ReferenceQueue 队列里,我们可以通过其 poll() 方法获取到这个被回收的对象的 WeakReference 实例,进而知道需要监控的对象是否被回收了。

关于内存泄漏

内存泄漏可能很容易发现,比如 Cursor 没关闭;比如在 Activity.onResume() 里 register 了某个需要监听的事件,但在 Activity.onPause() 里忘记 unregister 了;内存泄漏也可能很难发现,比如 LeakCanary 示例代码,隐含地引用,并且只有在旋转屏幕时才会发生。还有更难发现,甚至无能为力的内存泄漏,比如 Android SDK 本身的 BUG 导致内存泄漏。AndroidExcludedRefs.java 里就记录了一些己知的 AOSP 版本的以及其 OEM 实现版本里存在的内存泄漏。

本期推荐

推荐一个画图工具 planUML,其最大的特色是使用脚本来画图。它和 starUML 的最大区别是,前者是画图工具,类似于微软的 visio,而且支持脚本画图,后者是建模工具。这里是 planUML 的官方文档。它还支持一堆扩展,比如 Sublime Text等。本文的流程图就是用 planUML 画的。

时间: 2024-07-30 20:09:38

利用 LeakCanary 来检查 Android 内存泄漏的相关文章

Android —— 内存泄漏检查

今天地铁上看到一篇不错的将内存泄漏简单检查的文章,觉得还不错哟,内存泄漏确实是每个程序员头疼的事情,这里就多研究一下咯^^ 一. 常见的垃圾回收算法 参看文章 引用计数法 引用计数法基本上最简单的垃圾回收策略,它的核心思想是: 当有指针指向某实例时,计数加一, 当删除一个指针时,计数减一,当计数为0时,说明该实例没有引用可以被垃圾回收器回收. 这种回收策略的缺点是显而易见的: 1.维护引用计数是有开销的 2.计数的保存会消耗额外的空间 3.无法处理循环引用 标记清除 标记清除,顾名思义分为2步:

android 内存泄漏检测工具 LeakCanary 泄漏金丝雀

韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha [email protected] 内存泄漏检测工具 android 内存泄漏检测工具 ======== 内存泄漏 就是  无用的对象没有被回收,占用着内存,使得可用内存变小了. 如何检测内存泄漏, 可以使用 LeakCanary来检测内存泄漏. leak  是 泄漏的意思.. Canary 是 金丝雀 的意思. 在运行 应用的时候, 泄漏金丝雀 如果检测到内存泄漏 会显示一个通知. ======== LeakCanary捕获

android内存泄漏检测StrictMode和MAT工具使用

StrictMode说明 Android 2.3提供一个称为严苛模式(StrictMode)的调试特性,Google称该特性已经使数百个Android上的Google应用程序受益.那它都做什么呢?它将报告与线程及虚拟机相关的策略违例.一旦检测到策略违例(policy violation),你将获得警告,其包含了一个栈trace显示你的应用在何处发生违例.你可以强制用警告代替崩溃(crash),也可以仅将警告计入日志,让你的应用继续执行.策略的细节尚难确定,可以期待随Android的成熟Googl

Android 性能篇 -- 带你领略Android内存泄漏的前世今生

基础了解 什么是内存泄漏? 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗.内存泄漏并不是指物理上的内存消失,这里的内存泄漏是指由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费. Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是 静态分配 . 栈式分配 和 堆式分配 ,对应的三种存储策略使用的内存空间主要分别是 静态存储区(也称方法区) . 栈区 和 堆区 . ?? 静态存储区(方法区):主要存放 静态数据 . 全局

android内存泄漏系列- 分析hprof文件

转载请注明出处 http://www.cnblogs.com/weiwangnuanyang/p/5703702.html ,谢谢. 如果只是想确定一下某一个场景是否有内存泄漏,AndroidStadio的控制台就有一个好工具,反复操作观察曲线是否上扬,如果曲线上扬则说明内存泄漏 但是,上面的工具不够强大,不能看出内存中驻留的具体的类和类的引用关系. 下面就来重点介绍一下,解决android内存泄漏必备利器-Memory Analysis; 具体安装方式请移步度娘. 我们这里重点介绍如何利用Me

Android 内存泄漏总结

Java中的内存泄漏 java内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收.在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连:其次,这些对象是无用的,即程序以后不会再使用这些对象.如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存. 在C++中,内存泄漏的范围更大一些.有些对象被

Android内存泄漏

韩梦飞沙  韩亚飞  [email protected]  yue31313  han_meng_fei_sha #Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题.内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收.最近自己阅读了大量相关的文档资料,打算做个 总结 沉淀下来跟大家一起分享和学习,也给自己一个警示,以后 coding 时怎么避免这些情况,提高应用的体验

Android内存泄漏查找和解决

Android内存泄漏查找和解决 目录: 内存泄漏的概念 一个内存泄漏的例子 Java中"失效"的private修饰符 回头看内存泄漏例子泄漏的重点 强引用与弱引用 解决内部类的内存泄漏 Context造成的泄漏 使用LeakCanary工具查找内存泄漏 总结 一.内存泄漏概念 1.什么是内存泄漏? 用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元.直到程序结束.即所谓的内存泄漏. 其实说白了就是该内存空间使用完毕之后未回收 2.内存泄漏会导致的问题 内

新年过后献上关于Android内存泄漏的种种总结

Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问 题.内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一 直被某个或某些实例所持有却不再被使用导致 GC 不能回收 我会从 java 内存泄漏的基础知识开始,并通过具体例子来说明 Android 引起内存泄 漏的各种原因,以及如何利用工具来分析应用内存泄漏,最后再做总结. 篇幅有些长,大家可以分几节来看! (顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找)htt