Android内存泄漏检测利器:LeakCanary
Android内存泄漏检测利器:LeakCanary
是什么?
一言以蔽之:LeakCanary是一个傻瓜化并且可视化的内存泄露分析工具
为什么需要LeakCanary?
因为它简单,易于发现问题,人人可参与。
- 简单:只需设置一段代码即可,打开应用运行一下就能够发现内存泄露。而MAT分析需要Heap Dump,获取文件,手动分析等多个步骤。
- 易于发现问题:在手机端即可查看问题即引用关系,而MAT则需要你分析,找到Path To GC Roots等关系。
- 人人可参与:开发人员,测试测试,产品经理基本上只要会用App就有可能发现问题。而传统的MAT方式,只有部分开发者才有精力和能力实施。
如何集成
尽量在app下的build.gradle中加入以下依赖
1
2
3
4
5
|
|
在Application中加入类似如下的代码
1
2
3
4
5
6
7
|
|
到这里你就可以检测到Activity的内容泄露了。其实现原理是设置Application的ActivityLifecycleCallbacks方法监控所有Activity的生命周期回调。内部实现代码为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
|
想要检测更多?
首先我们需要获得一个RefWatcher,用来后续监控可能发生泄漏的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
|
监控某个可能存在内存泄露的对象
1
|
|
哪些需要进行监控
默认情况下,是对Activity进行了检测。另一个需要监控的重要对象就是Fragment实例。因为它和Activity实例一样可能持有大量的视图以及视图需要的资源(比如Bitmap)即在Fragment onDestroy方法中加入如下实现
1
2
3
4
5
6
7
|
|
其他也可以监控的对象
- BroadcastReceiver
- Service
- 其他有生命周期的对象
- 直接间接持有大内存占用的对象(即Retained Heap值比较大的对象)
何时进行监控
首先,我们需要明确什么是内存泄露,简而言之,某个对象在该释放的时候由于被其他对象持有没有被释放,因而造成了内存泄露。
因此,我们监控也需要设置在对象(很快)被释放的时候,如Activity和Fragment的onDestroy方法。
一个错误示例,比如监控一个Activity,放在onCreate就会大错特错了,那么你每次都会收到Activity的泄露通知。
如何解决
常用的解决方法思路如下
- 尽量使用Application的Context而不是Activity的
- 使用弱引用或者软引用
- 手动设置null,解除引用关系
- 将内部类设置为static,不隐式持有外部的实例
- 注册与反注册成对出现,在对象合适的生命周期进行反注册操作。
- 如果没有修改的权限,比如系统或者第三方SDK,可以使用反射进行解决持有关系
加入例外
有些特殊情况,我们需要忽略一些问题,这时候就需要添加例外规则。比如ExampleClass.exampleField会导致内存泄漏,我们想要忽略,如下操作即可。
1
2
3
4
5
6
7
8
9
|
|
如何实现的
LeakCanary实际上就是在本机上自动做了Heap dump,然后对生成的hprof文件分析,进行结果展示。和手工进行MAT分析步骤基本一致。
如何不影响对外版APK
是的,这个问题确实值得关注,因为LeakCanary确实是影响程序运行的,尤其是heap dump操作,不过好在这件事Square已经考虑了,即在我们增加依赖时
1
2
3
|
|
其中releaseCompile和testCompile这两个的依赖明显不同于debugCompile的依赖。它们的依赖属于NOOP操作。
NOOP,即No Operation Performed,无操作指令。常用的编译器技术会检测无操作指令并出于优化的目的将无操作指令剔除。
因而,只要配置好releaseCompile和testCompile的依赖,就无需担心对外版本的性能问题了。
实践中的问题
- 如果targetSdkVersion为23,在6.0的机器上会存在问题,卡死,因为LeakCanary并没有很好支持Marshmallow运行时权限,所以始终得不到sd卡权限,进而导致卡死。
注意
- 目前LeakCanary一次只能报一个泄漏问题,如果存在内存泄漏但不是你的模块,并不能说明这个模块没有问题。建议建议将非本模块的泄漏解决之后,再进行检测。
地址:http://droidyue.com/blog/2016/03/28/android-leakcanary/#
//==================================================
版权声明:本文为博主原创文章,未经博主允许不得转载。
也是看了一些内存分析的文章自己做点笔记,文字很多,印象最深的是http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html
这里我用的是MAT eclipse插件形式,安装和普通插件差不多,window---->install new soft···add
mat地址:http://download.eclipse.org/mat/1.2/update-site/(参照评论中朋友更新)
安装ok。
写个例子试下,
- public class MemoryTestActivity extends Activity
- {
- List list = new ArrayList();
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- while (true)
- {
- Object o=new Object();
- list.add(o);
- o=null;
- }
- }
- }
public class MemoryTestActivity extends Activity { List list = new ArrayList(); /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); while (true) { Object o=new Object(); list.add(o); o=null; } } }跑起来,看logcat信息,一大堆内存溢出的信息,如下
切换到ddms,选中要分析的应用程序,按下update heap ,再点击dump hprof file就会保存内存分析文件,MAT 安装没问题应该会自动打开的。我的安装ms有点问题,文件也需要手工转换,可以看到sdk的tools目录下有个hprof-conv 的工具 ,进命令行,切换到tools目录,利用命令hprof-conv xx yy.hprof 可以将刚才的内存分析文件转换成可以打开的,之后将eclipse切换到内存分析模式,如图,
file---》open file,打开刚才转换的文件。在视图overview里面可以清晰的看到内存占用情况,如图
点击leak Suspects,如图,我们从这里应该比较好分析定位了
很快就可以看出哪里造溢出了。
想要更深入的研究的朋友可以参照这位朋友的blog
http://www.blogjava.net/rosen/archive/2010/05/21/321575.html
http://www.blogjava.net/rosen/archive/2010/06/13/323522.html
android内存泄露的问题
最近写的一个程序中内存会不断增加,网上查找相关资料。整理如下:
0:原因:Java的内存管理与内存泄露(http://immortal.5d6d.com/thread-36-1-1.html)
Java内存泄漏是每个Java程序员都会遇到的问题,程序在本地运行一切正常,可是布署到远端就会出现内存无限制的增长,最后系统瘫痪,那么如何最快最好的检测程序的稳定性,防止系统崩盘,作者用自已的亲身经历与各位网友分享解决这些问题的办法。 作为Internet最流行的编程语言之一,Java现正非常流行。我们的网络应用程序就主要采用Java语言开发,大体上分为客户端、服务器和数据库三个层次。在进入测试过程中,我们发现有一个程序模块系统内存和CPU资源消耗急剧增加,持续增长到出现java.lang.OutOfMemoryError为止。经过分析Java内存泄漏是破坏系统的主要因素。这里与大家分享我们在开发过程中遇到的Java内存泄漏的检测和处理解决过程. 本文先介绍Java的内存管理,以及导致Java内存泄露的原因。 一. Java是如何管理内存 为了判断Java中是否有内存泄露,我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。 Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就作为垃圾回收。GC为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。 在Java中,这些无用的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。虽然,我们有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义,该函数不保证JVM的垃圾收集器一定会执行。因为不同的JVM实现者可能使用不同的算法管理GC。通常GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。 二. 什么是Java中的内存泄露 导致内存泄漏主要的原因是,先前申请了内存空间而忘记了释放。如果程序中存在对无用对象的引用,那么这些对象就会驻留内存,消耗内存,因为无法让垃圾回收器GC验证这些对象是否不再需要。如果存在对象的引用,这个对象就被定义为"有效的活动",同时不会被释放。要确定对象所占内存将被回收,我们就要务必确认该对象不再会被使用。典型的做法就是把对象数据成员设为null或者从集合中移除该对象。但当局部变量不需要时,不需明显的设为null,因为一个方法执行完毕时,这些引用会自动被清理。 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是有被引用的,即在有向树形图中,存在树枝通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。 这里引用一个常看到的例子,在下面的代码中,循环申请Object对象,并将所申请的对象放入一个Vector中,如果仅仅释放对象本身,但因为Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。 Vector v = new Vector(10); for (int i = 1; i < 100; i++) { Object o = new Object(); v.add(o); o = null; }//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。 实际上这些对象已经是无用的,但还被引用,GC就无能为力了(事实上GC认为它还有用),这一点是导致内存泄漏最重要的原因。 再引用另一个例子来说明Java的内存泄漏。假设有一个日志类Logger,其提供一个静态的log(String msg),任何其它类都可以调用Logger.Log(message)来将message的内容记录到系统的日志文件中。 Logger类有一个类型为HashMap的静态变量temp,每次在执行log(message)的时候,都首先将message的值写入temp中(以当前线程+当前时间为键),在退出之前再从temp中将以当前线程和当前时间为键的条目删除。注意,这里当前时间是不断变化的,所以log在退出之前执行删除条目的操作并不能删除执行之初写入的条目。这样,任何一个作为参数传给log的字符串最终由于被Logger的静态变量temp引用,而无法得到回收,这种对象保持就是我们所说的Java内存泄漏。 总的来说,内存管理中的内存泄漏产生的主要原因:保留下来却永远不再使用的对象引用。 |
1:查找内存泄露的方法。
与C++的内存不同,C++的内存泄露是由于分配了内存给某程序但是又没有回收造成的。Java的内存泄露则是引用了一些垃圾对象,意思就是说程序引用了某些对象,但是又从来没有使用过。
Jave中的引用分为3种:
强引用:引用为空的时候,Java的垃圾回收器会处理。一般来说自己写的程序大部分都是强引用。
软引用:堆内存不够的时候,Java的垃圾回收器会处理这类引用。
弱引用:Jave的垃圾回收器每次都会回收这类引用。
如何用MAT来分析,前提是Android开发和测试的工具安装完整,SDK,Eclipse:
1.打开Eclipse
2.选择 Help->Install New Software;
3.在Work with中添加站点:http://download.eclipse.org/mat/1.0/update-site/(这个地址可能会变化,但是新的地址可以在官方网站上找到:http://www.eclipse.org/mat/downloads.php )
4.生成.hprof文件:插入SD卡(Android机器很多程序都需要插入SD卡),并将设备连接到PC,在Eclipse中的DDMS中选择要测试的进程,然后点击Update Heap 和Dump HPROF file两个Button。
.hprof 文件会自动保存在SD卡上,把 .hprof 文件拷贝到PC上的\ android-sdk-windows\tools目录下。这个由DDMS生成的文件不能直接在MAT打开,需要转换。
运行cmd打开命令行,cd到\ android-sdk-windows\tools所在目录,并输入命令hprof-conv xxxxx.hprof yyyyy.hprof,其中xxxxx.hprof为原始文件,yyyyy.hprof为转换过后的文件。转换过后的文件自动放在android-sdk-windows\tools 目录下。
OK,到此为止,.hprof文件处理完毕,可以用来分析内存泄露情况了。
5.打开MAT:
在Eclipse中点击Windows->Open Perspective->Other->Memory Analysis
6.导入.hprof文件
在MAT中点击 File->Open File,浏览到刚刚转换而得到的.hprof文件,并Cancel掉自动生成报告,点击Dominator Tree,并按Package分组,选择自己所定义的Package 类点右键,在弹出菜单中选择List objects->With incoming references。
这时会列出所有可疑类,右键点击某一项,并选择Path to GC Roots->exclude weak/soft references,会进一步筛选出跟程序相关的所有有内存泄露的类。据此,可以追踪到代码中的某一个产生泄露的类。
2:相关知识
Android将进程分为六大类:
前台进程(foreground):目前正在屏幕上显示的进程和一些系统进程。举例来说,Dialer Storage,Google Search等系统进程就是前台进程;再举例来说,当你运行一个程序,如浏览器,当浏览器界面在前台显示时,浏览器属于前台进程(foreground),但一旦你按home回到主界面,浏览器就变成了后台程序(background)。我们最不希望终止的进程就是前台进程。
可见进程(visible):可见进程是一些不再前台,但用户依然可见的进程,举个例来说:widget、输入法等,都属于visible。这部分进程虽然不在前台,但与我们的使用也密切相关,我们也不希望它们被终止(你肯定不希望时钟、天气,新闻等widget被终止,那它们将无法同步,你也不希望输入法被终止,否则你每次输入时都需要重新启动输入法)
次要服务(secondary server):目前正在运行的一些服务(主要服务,如拨号等,是不可能被进程管理终止的,故这里只谈次要服务),举例来说:谷歌企业套件,Gmail内部存储,联系人内部存储等。这部分服务虽然属于次要服务,但很一些系统功能依然息息相关,我们时常需要用到它们,所以也太希望他们被终止。
后台进程(hidden):虽然作者用了hidden这个词,但实际即是后台进程(background),就是我们通常意义上理解的启动后被切换到后台的进程,如浏览器,阅读器等。当程序显示在屏幕上时,他所运行的进程即为前台进程(foreground),一旦我们按home返回主界面(注意是按home,不是按back),程序就驻留在后台,成为后台进程(background)。后台进程的管理策略有多种:有较为积极的方式,一旦程序到达后台立即终止,这种方式会提高程序的运行速度,但无法加速程序的再次启动;也有较消极的方式,尽可能多的保留后台程序,虽然可能会影响到单个程序的运行速度,但在再次启动已启动的程序时,速度会有所提升。这里就需要用户根据自己的使用习惯找到一个平衡点
内容供应节点(content provider):没有程序实体,进提供内容供别的程序去用的,比如日历供应节点,邮件供应节点等。在终止进程时,这类程序应该有较高的优先权
空进程(empty):没有任何东西在内运行的进程,有些程序,比如BTE,在程序退出后,依然会在进程中驻留一个空进程,这个进程里没有任何数据在运行,作用往往是提高该程序下次的启动速度或者记录程序的一些历史信息。这部分进程无疑是应该最先终止的。系统会对进程的重要性进行评估,并将重要性以“oom_adj”这个数值表示出来,赋予各个进程;(系统会根据“oom_adj”来判断需要结束哪些进程,一般来说,“oom_adj”的值越大,该进程被系统选中终止的可能就越高)
前台程序的“oom_adj”值为0,这意味着它不会被系统终止,一旦它不可访问后,会获得个更高的“oom_adj”,作者推测“oom_adj”的值是根据软件在LRU列表中的位置所决定的;
Android不同于Linux,有一套自己独特的进程管理模块,这个模块有更强的可定制性,可根据“oom_adj”值的范围来决定进程管理策略,比如可以设定“当内存小于X时,结束“oom_adj”大于Y的进程”。这给了进程管理脚本的编写以更多的选择。
内存泄露,是Android开发者最头疼的事。可能一处小小的内存泄露,都可能是毁于千里之堤的蚁穴。
怎么才能检测内存泄露呢?网上教程非常多,不过很多都是使用Eclipse检测的, 其实1.3版本以后的Android Studio 检测内存非常方便, 如果结合上MAT工具,LeakCanary插件,一切就变得so easy了。
熟悉Android Studio界面
工欲善其事,必先利其器。我们接下来先来熟悉下Android Studio的界面
一般分析内存泄露, 首先运行程序,打开日志控制台,有一个标签MemZ喎�"/kf/ware/vc/" target="_blank" class="keylink">vcnkgLM7Sw8e/ydLU1NrV4rj2vefD5rfWzva1scews8zQ8sq508O1xMTatObH6b/2LCDSu8S/wcvIuywgztLDx9TZ0rKyu9Do0qq/4L/gtcTU2mxvZ2NhdNbQ0bDV0sTatOa1xMjV1r7By6GjPC9wPgoKPHA+PHN0cm9uZz7NvNbQwLbJq8f40/KjrL7NysezzNDyyrnTw7XExNq05qOsILvSyavH+NPyvs3Kx7/Vz9DE2rTmo6w8L3N0cm9uZz4gPGJyPgq1sci7o6xBbmRyb2lkxNq05rfWxeS7+tbGyse21MO/uPbTptPDs8zQ8tbwsr3U9rzTLCCxyMjnxOOzzNDytbHHsMq508MzME3E2rTmLCDPtc2zv8nE3LvhuPjE47fWxeQ0ME0sILWxx7C+zdPQMTBNv9XP0CwgyOe5+7PM0PLKudPDwcs1ME3ByyzPtc2zu+G99L3T18W4+LWxx7CzzNDy1Pa809K7sr+31iyxyMjntO+1vcHLODBNo6wgILWxx7DE47XEv9XP0MTatOa+zcrHMzBNwcuhoyAgtbHIuyzPtc2zyOe5+7K7xNzU2bj4xOO31sXktu7N4rXExNq05iyzzNDy19TIu77Nu+FPT00oxNq05tLns/YpwcuhoyDDv7j206bTw7PM0PLX7rjfv8nS1Mnqx+u1xMTatOa6zcrWu/rD3MfQz+C52KOsscjI587StbHHsMq508O1xLuqzqpNYXRlNyy8q8/etPO4xcrHMjAwTSzL47HIvc+437XEwcssICDSu7DjMTI4TSC+zcrHvKvP3sHLLCDJ9dbB09C1xMrWu/rWu9PQv8nBr7XEMTZNu/LV3zMyTaOs1eLR+bXEyta7+s/gttTT2sTatObS57P2tcS4xcLKt8ezo7TzwcuhozwvcD4KCgoKPGgyIGlkPQ=="我们怎么检测内存泄露呢">我们怎么检测内存泄露呢
首先需要明白一个概念, 内存泄露就是指,本应该回收的内存,还驻留在内存中。
一般情况下,高密度的手机,一个页面大概就会消耗20M内存,如果发现退出界面,程序内存迟迟不降低的话,可能就发生了严重的内存泄露。
我们可以反复进入该界面,然后点击dump java heap 这个按钮,然后Android Studio就开始干活了,下面的图就是正在dump
dump成功后会自动打开 hprof文件,文件以Snapshot+时间来命名
通过Android Studio自带的界面,查看内存泄露还不是很智能,我们可以借助第三方工具,常见的工具就是MAT了,下载地址 http://eclipse.org/mat/downloads.php ,这里我们需要下载独立版的MAT. 下图是MAT一开始打开的界面, 这里需要提醒大家的是,MAT并不会准确地告诉我们哪里发生了内存泄漏,而是会提供一大堆的数据和线索,我们需要自己去分析这些数据来去判断到底是不是真的发生了内存泄漏。
接下来我们需要用MAT打开内存分析的文件, 上文给大家介绍了使用Android Studio生成了 hprof文件, 这个文件在呢, 在Android Studio中的Captrues这个目录中,可以找到
注意,这个文件不能直接交给MAT, MAT是不识别的, 我们需要右键点击这个文件,转换成MAT识别的。
然后用MAT打开导出的hprof(File->Open heap dump) MAT会帮我们分析内存泄露的原因
LeakCanary
上面介绍了MAT检测内存泄露, 再给大家介绍LeakCanary。
项目地址:https://github.com/square/leakcanary
LeakCanary会检测应用的内存回收情况,如果发现有垃圾对象没有被回收,就会去分析当前的内存快照,也就是上边MAT用到的.hprof文件,找到对象的引用链,并显示在页面上。这款插件的好处就是,可以在手机端直接查看内存泄露的地方,可以辅助我们检测内存泄露
使用:
在build.gradle文件中添加,不同的编译使用不同的引用:
1
2
3
4
|
<code class = " hljs matlab" >dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3' }</code> |
在应用的Application onCreate方法中添加LeakCanary.install(this),如下
1
2
3
4
5
6
7
|
<code class = " hljs java" > public class ExampleApplication extends Application @Override public void onCreate() { super .onCreate(); LeakCanary.install( this ); } }</code> |
应用运行起来后,LeakCanary会自动去分析当前的内存状态,如果检测到泄漏会发送到通知栏,点击通知栏就可以跳转到具体的泄漏分析页面。
Tips:就目前使用的结果来看,绝大部分泄漏是由于使用单例模式hold住了Activity的引用,比如传入了context或者将Activity作为listener设置了进去,所以在使用单例模式的时候要特别注意,还有在Activity生命周期结束的时候将一些自定义监听器的Activity引用置空。
关于LeakCanary的更多分析可以看项目主页的介绍,还有这里http://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
追踪内存分配
如果我们想了解内存分配更详细的情况,可以使用Allocation Traker来查看内存到底被什么占用了。
用法很简单:
点一下是追踪, 再点一下是停止追踪, 停止追踪后 .alloc文件会自动打开,打开后界面如下:
当你想查看某个方法的源码时,右键选择的方法,点击Jump to source就可以了
查询方法执行的时间
Android Studio 功能越来越强大了, 我们可以借助AS观测各种性能,如下图:
如果我们要观测方法执行的时间,就需要来到CPU界面
点击Start Method Tracking, 一段时间后再点击一次, trace文件被自动打开,
非独占时间: 某函数占用的CPU时间,包含内部调用其它函数的CPU时间。
独占时间: 某函数占用CPU时间,但不含内部调用其它函数所占用的CPU时间。
我们如何判断可能有问题的方法?
通过方法的调用次数和独占时间来查看,通常判断方法是:
如果方法调用次数不多,但每次调用却需要花费很长的时间的函数,可能会有问题。 如果自身占用时间不长,但调用却非常频繁的函数也可能会有问题。
综述
上面给大家介绍了若干使用Android Studio检查程序性能的工具,工具永远是辅助,不要因为工具耽误太长时间。如果有问题,欢迎大家纠正。