标签:android outofmemoryerror 内存泄漏 oom
你被概率性的 OOM 困扰么?有时候,OOM 像幽灵一样,挥之不去,可真想把它揪出来时,又捉之不着。或许,是时候用 LeakCanary 来诊断一下了。它是一个用来检查 Android 下内存泄漏的开源库,这篇文章主要介绍其用法、架构和其背后的实现原理。
Square 有篇文章介绍了开发这个库的原因。他们的一个付款流程里,需要用到用户的签名,他们直接用 Bitmap 来画签名,Bitmap 大小和屏幕分辨率是一样的。问题来了,在试图创建这个 Bitmap 对象时,概率性 OOM 如幽灵般相随。他们试了几个方法:
Bitmap.Config.ALPHA_8
来节省内存OutOfMemoryError
异常,调用 gc 清理内存,然后重试几次最终这些都不起作用。最终他们发现他们在错误的方向上走得太远了。当存在内存泄漏时,可用内存越来越少,这个时候 OOM 可以发生在任何地方,特别是试图创建一些大内存对象,如 Bitmap 的时候。
我们在上一篇文章《Android 内存与性能》里介绍了使用 MAT 来分析内存泄漏的方法。概括起来核心步骤是:
如果有一个工具能自动完成这些事情,甚至在发生 OOM 之前,就把内存泄漏报告给你,那是多么美好的一件事情啊。LeakCanary 就是用来干这个事情的。在测试你的 App 时,如果发生了内存泄漏,状态栏上会有通知告诉你。logcat 上也会有相应的 log 通知你。
启发
LeakCanary 产生的背后有几个有意思的启发。一是像 Square 这样公司一样会被 OOM 这种问题困扰;二是他们也会犯错,试了几种方法都不起作用;三是他们最终用一个优雅的方式解决了这个问题,并且通过开源库的方式让所有人共享他们的工作成果。
我们经常把 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)
来实现。
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 里看到内存泄漏的提示。
dependencies { debugCompile ‘com.squareup.leakcanary:leakcanary-android:1.3‘ releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.3‘ }
在 debug 版本上,集成 LeakCanary 库,并执行内存泄漏监测,而在 release 版本上,集成一个无操作的 wrapper ,这样对程序性能就不会有影响。
LeakCanary 的机制如下:
RefWatcher.watch()
会以监控对象来创建一个 KeyedWeakReference
弱引用对象AndroidWatchExecutor
的后台线程里,来检查弱引用已经被清除了,如果没被清除,则执行一次
GCHeapAnalyzerService
启动一个单独的进程,使用 HeapAnalyzer
来分析
hprof 文件。它使用另外一个开源库 HAHA。HeapAnalyzer
通过查找 KeyedWeakReference
弱引用对象来查找内在泄漏HeapAnalyzer
计算 KeyedWeakReference
所引用对象的最短强引用路径,来分析内存泄漏,并且构建出对象引用链出来。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 画的。
利用 LeakCanary 来检查 Android 内存泄漏
标签:android outofmemoryerror 内存泄漏 oom
原文地址:http://blog.csdn.net/kamidox/article/details/45676469