前言
简介
Squar LeakCanary :https://github.com/square/leakcanary
A memory leak detection library for Android and Java.
一个Android和Java的内存泄露检测库。
“A small leak will sink a great ship.” - Benjamin Franklin
小漏不补沉大船。——本杰明 富兰克林
为什么要做内存泄漏检测
当内存吃紧的时候,到处都有可能引发 OOM,OOM更深层次的问题可能是: 内存泄露。
一些对象有着有限的生命周期。当这些对象所要做的事情完成了,我们希望他们会被回收掉。但是如果有一系列对这个对象的引用,那么在我们期待这个对象生命周期结束的时候被收回的时候,它是不会被回收的。它还会占用内存,这就造成了内存泄露。持续累加,内存很快被耗尽。
比如,当 Activity.onDestroy 被调用之后,activity 以及它涉及到的 view 和相关的 bitmap 都应该被回收。但是,如果有一个后台线程持有这个 activity 的引用,那么 activity 对应的内存就不能被回收。这最终将会导致内存耗尽,然后因为 OOM 而 crash。
因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。
Android中常见的内存泄漏汇总
1、集合类泄漏
集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。比如上面的典型例子就是其中一种情况,当然实际上我们在项目中肯定不会写这么 2B 的代码,但稍不注意还是很容易出现这种情况,比如我们都喜欢通过 HashMap 做一些缓存之类的事,这种情况就要多留一些心眼。
2、单例造成的内存泄漏
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。
3、匿名内部类/非静态内部类和异步线程
- 非静态内部类创建静态实例造成的内存泄漏
- 匿名内部类
- Handler 造成的内存泄漏
解决:推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。
4、尽量避免使用 static 成员变量
从前对战内存泄露
排查内存泄露是一个全手工的过程,以下几个关键步骤:
1.通过某某统计平台,了解 OutOfMemoryError 情况。
2.重现问题。为了重现问题,机型非常重要,因为一些问题只在特定的设备上会出现。 当然,为了确定复现步骤,你需要一遍一遍地去尝试。
3.在发生内存泄露的时候,把内存 Dump 出来。
4.然后,你需要在 MAT 之类的内存分析工具中反复查看,找到那些原本该被回收掉的对象。
5.计算这个对象到 GC roots 的最短强引用路径。
6.确定引用路径中的哪个引用是不该有的,然后修复问题。
正文
开始使用
在 build.gradle
中加入引用,不同的编译使用不同的引用:
1 | dependencies { |
In your Application
class:
1 | public class ExampleApplication extends Application { |
当在你的debug构建过程中出现内存泄露时,LeakCanary将会自动展示一个通知栏。
当点击该通知时,会跳转到具体的页面,展示出Leak的引用路径,如下图所示:
如何使用
使用RefWatcher
监控那些本该被GC回收的对象。
1 | RefWatcher refWatcher = {...}; |
LeakCanary.install()
会返回一个预定义的RefWatcher
,同时也会启用一个 ActivityRefWatcher
,用于自动监控调用Activity.onDestroy()
之后泄露的 activity。
1 | public class ExampleApplication extends Application { |
使用RefWatcher
监控Fragment
:
1 | public abstract class BaseFragment extends Fragment { |
工作机制
1.RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
2.然后在后台线程检查引用是否被清除,如果没有,调用GC。
3.如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
4.在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
5.得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
6.HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
7.引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
1 | private final Set<String> retainedKeys; |
RefWatcher 就是通过弱引用及其队列来实现监控的:
有两个很重要的结构: retainedKeys 和 queue
retainedKeys 代表没被gc 回收的对象,
queue中的弱引用代表的是被gc了的对象,
通过对比两个结构就可以监控对象是不是被回收了;
retainedKeys存放了RefWatcher为每个被监控的对象生成的唯一key;
同时每个被监控对象的弱引用(KeyedWeakReference)关联了 其对应的key 和 queue,这样对象若被回收,则其对应的弱引用会被入队到queue中;
removeWeaklyReachableReferences(..)
所做的就是把存在与queue中的弱引用的key 从 retainedKeys 中删除。
如何复制 leak trace?
看查看复制,也可以通过分享按钮把这些东西分享出去。
leak trace 之外
有时,leak trace 不够清晰,你需要通过 MAT 或者 YourKit 深挖 dump 文件。
通过以下方法,你能找到问题所在:
1.查找所有的 com.squareup.leakcanary.KeyedWeakReference 实例。
2.检查 key 字段
3.Find the KeyedWeakReference that has a key field equal to the reference key reported by LeakCanary.
4.找到 key 和 和 logcat 输出的 key 值一样的 KeyedWeakReference。
5.referent 字段对应的就是泄露的对象。
6.剩下的,就是动手修复了。最好是检查到 GC root 的最短强引用路径开始。
参考
欢迎转载,请注明本文的链接地址: http://blog.neday.cn/2018/03/02/LeakCanary——Android和Java的内存泄露检测库/