Markdown版本笔记 |
我的GitHub首页 |
我的博客 |
我的微信 |
我的邮箱 |
MyAndroidBlogs |
baiqiantao |
baiqiantao |
bqt20094 |
[email protected] |
使用LeakCanary检测内存泄露 翻译 MD
原文
GitHub
Nov 18 2015
目录
目录
中文翻译
介绍 (0:00)
内存泄漏:非技术讲解 (1:40)
LeakCanary 救援 (3:47)
技术讲解内存泄漏 (8:06)
分析堆 (10:16)
LeakCanary 救你于水火 (12:04)
LeakCanary API 演练 (13:32)
什么是弱引用 (14:17)
HAHA 内存分析器 (16:55)
LeakCanary 的实现 (19:19)
Debug 一个真实的例子 (22:12)
忽略 SDK Crashes (28:10)
LeakCanary 的未来 (29:14)
Q&A (31:50)
中文翻译
我们的 App 曾经遇到很多的内存泄漏导致 OutOfMemoryError
的崩溃,一些甚至是在生产环境。Square 的 Pierre-Yvews Ricau 开发了 LeakCanary
最终解决了这些问题。LeakCanary
是一个帮助你检测和修复内存泄漏的工具。在这个分享中,Pierre 教授大家如何修复内存泄漏的错误,让你的 App 更稳定和可靠。
介绍 (0:00)
大家好,我是 Pierre-Yvews Ricau (叫我 PY 就行),现在在 Square 工作。
Square 出了一款名为:Square Register 的 App, 帮助你用移动设备完成支付。在用这个 App 的时候,用户先要登陆他的个人账号。
不幸的是,在签名页面有的时候会因为内存溢出而出现崩溃。老实说,这个崩溃来的太不是时候了 — 用户和商家都无法确认交易是否完成了,更何况是在和钱打交道的时候。我们也强烈的意识到,我们需要处理下内存溢出或者内存泄露这种事情了。
内存泄漏:非技术讲解 (1:40)
我想要聊的内存泄露解决方案是: LeakCanary。
LeakCanary 是一个可以帮助你发现和解决内存泄露的开源工具。
但是到底什么是内存泄露呢?我们从一个非技术角度来开始,先来举个例子。
...
有外部的引用指向了本不应该再指向的对象。类似这样的小规模的内存泄露堆积以后就会造成大麻烦。
LeakCanary 救援 (3:47)
这就是我们为什么要开发 LeakCanary。
我现在可能已经清楚了 可被回收的 Android 对象应该及时被销毁。
但是我还是没法清楚的看到这些对象是否已经被回收掉。有了 LeakCanary 以后,我们:
给可被回收的 Android 对象上打了智能标记,智能标记能知道他们所指向的对象是否被成功释放掉。
如果过一小段时间对象依然没有被释放,他就会给内存做个快照。LeakCanary 随后
会把结果发布出来:
帮助我们查看到内存到底怎么泄露了,并清晰的向我们展示那些无法被释放的对象的引用链。
举个具体的例子:在我们的 Square App 里的签名页面。用户准备签名的时候,App 因为内存溢出出错崩溃了。我们不能确认内存错误到底出在哪儿了。
签名页面持有了一个很大的有用户签名的 Bitmap 图片对象。图片的大小和用户手机屏幕大小一致 — 我们猜测这个有可能会造成内存泄露。首先,我们可以配置 Bitmap 为 alpha 8-bit 来节省内存。这是很常见的一种修复方案,而且效果也不错。但是并没有彻底解决问题,只是减少了泄露的内存总量。但是内存泄露依然在哪儿。
最主要的问题是我们 App 的堆满了,应该要留有足够的空间给我们的签名图片,但是由于很多处的内存泄露叠加在一起占用了很多内存。
技术讲解内存泄漏 (8:06)
假设,我有一个 App,这个 App 点一下就能买一个法棍面包。
private static Button buyNowButton;
由于某种原因,我把这个 button 设置成了 static
的。问题随之而来:
这个按钮除非你设置成了null,不然就内存泄露了!
你也许会说:“只是一个按钮而已,没啥大不了”。问题是这个按钮还有一个成员变量:叫 mContext
,这个东西指向了一个 Acitvity
,Acitivty 又指向了一个 Window
,Window 又拥有整个 View 继承树。算下来,那可是一大段的内存空间。
静态的变量是 GC root 类型的一种。垃圾回收器会尝试回收所有非 GC root 的对象
,或者某些被 GC root 持有的对象
。所以如果你创建一个对象,并且移除了这个对象的所有指向,他就会被回收掉。但是一旦你将一个对象设置成 GC root,那他就不会被回收掉。
当你看到类似“法棍按钮”的时候,很显然这个按钮持有了一个 Activity 的引用,所以我们必须清理掉它。当你沉浸在你的代码的时候,你肯定很难发现这个问题。你可能只看到了引出的引用。你可以知道 Activity 引用了一个 Window,但是谁引用了 Activity?
你可以用像 IntelliJ
这样的工具做些分析,但是它并不会告诉你所有的东西。通常,你可以把这些 Object 的引用关系组织成图,但是是个单向图。
分析堆 (10:16)
我们能做些什么呢?我们来做个快照。
我们拿出所有的内存然后导出到文件里,这个文件会被用来分析和解析堆结构。
其中一个工具叫做 Memory Analyzer,也叫 MAT。
它会通过 dump 的内存,然后分析所有存活在内存中的对象和类。
你可以用 SQL 对他做些查询,类似如下:
SELECT * FROM INSTANCEOF android.app.Activity a WHERE a.mDestroyed = true
这条语句会返回所有的状态为 destroyed
的实例。一旦你发现了泄露的 Activity,你可以执行 merge_shortest_paths
的操作来计算出最短的 GC root 路径
。从而找出阻止你 Acitivty 释放的那个对象。
之所以说要 “最短路径”,是因为通常从一个 GC root 到 Acitivty,有很多条路径可以到达。比如说:我的按钮的 parent view
,同样也持有一个 mContext 对象。
当我们看到内存泄露的时候,我们通常不需要去查看所有的这些路径。我们只需要最短的一条。那样的话,我们就排除了噪音,很快的找到问题所在。
LeakCanary 救你于水火 (12:04)
有 MAT 这样一个帮我们发现内存泄露的工具是个很棒的事情。但是在一个正在运行的 App 的上下文中,我们很难像我们的用户发现泄露那样发现问题所在。我们不能要求他们在做一遍相同操作,然后留言描述,再把 70MB+
的文件发回给我们。我们可以在后台做这个,但是并不 Cool。我们期望的是,我们能够尽早的发现泄露
,比如在我们开发的时候就发现这些问题。这也是 LeakCanary 诞生的意义。
一个 Activity 有自己生命周期。你了解它是如何被创建的,如何被销毁的,你期望他会在 onDestroy() 函数调用后,回收掉你所有的空闲内存
。如果你有一个能够检测一个对象是否被正常的回收掉了的工具
,那么你就会很惊讶的喊出:“这个可能造成内存泄露!它本该被回收掉,但却没有被垃圾回收掉!”
Activity 无处不在。很多人都把 Activity 当做神级 Object 一般的存在,因为它可以操作 Services,文件系统等等。经常会发生对象泄漏的情况,如果泄漏对象还持有 context 对象,那 context 也就跟着泄漏了。
Resources resources = context.getResources();
LayoutInflater inflater = LayoutInflater.from(context);
File filesDir = context.getFilesDir();
InputMethodManager inputMethodManager =(InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
LeakCanary API 演练 (13:32)
我们回过头来再看看智能标记smart pin
,我们希望知道的是当生命后期结束后,发生了什么。幸运的时,LearkCanary有一个很简单的 API。
第一步:创建 RefWatcher
。这里的Ref 其实是 Reference
的缩写。给 RefWatcher 传入一个对象的实例,它会检测这个对象是否被成功释放掉。
RefWatcher refWatcher = LeakCanary.install(this);
第二步:监听 Activity 生命周期。然后,当 onDestroy 被调用的时候,我们传入 Activity。
refWatcher.watch(this);// Make sure you don’t get installed twice.
什么是弱引用 (14:17)
想要了解这个是怎么工作的,我得先跟大家聊聊弱引用Weak References
。
我刚才提到过静态域的变量会持有Activity 的引用。所以刚才说的“下单”按钮就会持有 mContext 对象,导致 Activity 无法被释放掉。这个被称作强引用Strong Reference
。
一个对象可以有很多的强引用,在垃圾回收过程中,当这些强引用的个数总和为零的时候,垃圾回收器就会释放掉它。
弱引用就是一种不增加引用总数的持有引用方式,垃圾回收期是否决定要回收一个对象,只取决于它是否还存在强引用。
所以说,如果我们:
将我们的 Activity 持有为弱引用,一旦我们发现弱引用持有的对象已经被销毁了,那么这个 Activity 就已经被垃圾回收器回收了。
否则,那可以大概确定这个 Activity 已经被泄露了。
弱引用的主要目的是为了做 Cache,而且非常有用。主要就是告诉 GC,尽管我持有了这个对象,但是如果一旦没有对象在用这个对象的时候,GC 就可以在需要的时候销毁掉。
在下面的例子中,我们继承了 WeakReference:
final class KeyedWeakReference extends WeakReference