Memory Leak检測神器--LeakCanary初探
在之前的文章Android内存泄露的几种情形中提到过在开发中常见的内存泄露问题,可是过于草率。因为刚开年,工作还没正式展开,就看了一下Github开源大户Square的LeakCanary,并用公司项目的測试环境来练手。试图找出项目中存在的内存泄露。与上一篇不同,这一篇我会先说一下Java的内存区域以及垃圾回收机制,然后再讲LeakCanary的应用。而且会用一个在项目中遇到的真实案例来结尾。
Java的内存模型
在对于LeakCanary来说,我们主要关心Java程序执行时的堆和栈。
堆是用来存放对象的地方。栈是用来存放引用的地方。引用通过对象的句柄或者对象的地址来与对象保持关联。
垃圾回收就发生在堆上。
Java垃圾回收算法
垃圾回收算法有非常多种,这里介绍Java中常见的垃圾回收算法:
垃圾回收器(GC)把栈上的一些引用所关联的对象作为根节点(GC Root),依据这些引用去搜索与其关联的对象。搜索所经过的节点所组成的路径称为GC链。比方有三个类A,B,C,当中,A持有B的应用,B持有C的引用,
public class A {
public A(B b)
{
this.b = b;
}
private B b;
}
public class B {
public B(C c){
this.c = c;
}
private C c;
}
public class C {
}
当执行:
C c = new C();
B b = new B(c);
A a= new A(b);
我们就能够通过引用a来找到C的对象。这一条链就能够作为GC链。
当一个对象从GC Root有路径可达,就说明这个对象正在被引用。
GC对于这样的对象会“网开一面”。假设有对象没有不论什么GC Root可达。GC就会对这些对象打上标记,方便后面回收。
讲到这里有必要再介绍一下 内存泄露。当一个对象的“使命完毕”的时候,依照我们的意愿,此时GC应该回收这部分对象的内存空间。比如:一个方法里面包括有一个局部变量A,当这种方法执行完以后。我们希望A非常快被回收。可是因为一些原因没有回收。我们就说发生了内存泄露。为什么会有内存泄露?说究竟就是因为这时从GC Root到此对象是可达的。对于我们Android来说,Android非常多组件都有生命周期的概念,比如:Activity,Fragment。当这些组件的生命周期结束(onDestroy方法被回调)时,这些组件应该被回收掉。
可是因为一些原因。比方:Activity被一个生命周期比較长的匿名内部类引用。被一个static对象引用。被Handler(通常是Handler调用了postDelay方法)引用。。。等情况。
Android对每一个进程的内存占用是有限制大小的,曾经在16MB以内。这就要求我们对内存的使用十分小心。内存泄露导致对象甚至Android组件(通常包括非常多其它引用,占用内存大)不能被回收。就会对程序安全在成极大的隐患,有可能用户在一个会引发内存泄露的动作上重复操作。使内存在非常短时间内急剧膨胀,最后造成程序闪退的“悲慘结局”。然而这样的结局都不是我们想要的,所以,我们应该尽量做到不让程序产生内存泄露。因为内存泄露。并不会像空指针这样的错误一样直接抛出来,普通程序猿非常难发现内存泄露带来的隐患。
据统计。94%得OOM异常都是因为内存泄露引发的。所以,解决内存泄露是我们Android程序猿必须面对的话题。
内存泄露检測神器LeakCanary
LeakCananry是开源大户Square的一款开源产品。用于检測程序中的内存泄露。easy上手,操作简单,是广大安卓程序猿的必备神器。
GItHUB项目地址。
集成LeakCanary
因为公司项目还是在Eclipse上面开发,所以这里说的是怎样在Eclipse里面集成。
首先我们下载适用于Eclipse的LeakCanary。项目地址。在此感谢作者的辛勤劳动。
然后。我们在Eclipse将下载的包import到Eclipse工作空间。将其作为Android的库(library)。
接着,我们将LeakCanary里面的Service和Activity复制到你的项目里面。记得将Service和Activity的名字改成全类名。改动好的清单文件大致为:
.........
.........
你项目的清单
.........
<!-- Leakcanary必须的界面和服务 -->
<service
android:name="com.squareup.leakcanary.internal.HeapAnalyzerService"
android:enabled="false"
android:process=":leakcanary" />
<service
android:name="com.squareup.leakcanary.DisplayLeakService"
android:enabled="false" />
<activity
android:name="com.squareup.leakcanary.internal.DisplayLeakActivity"
android:enabled="false"
android:icon="@drawable/__leak_canary_icon"
android:label="@string/__leak_canary_display_activity_label"
android:taskAffinity="com.squareup.leakcanary"
android:theme="@style/__LeakCanary.Base" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
至此,LeakCanary集成完毕。
在项目中使用LeakCanary
我们须要在Application里面对LeakCanary做初始化,然后在BaseActivity或者BaseFragment的onDestroy里面对这个类进行监控。代码为:
/**
* 初始化内存泄露监測 applicaton里面的代码
*/
private void initRefWatcher() {
this.refWatcher = LeakCanary.install(this);
}
//BaseActivity或者BaseFragment的代码
@Override
protected void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = MentorNowApplication.getRefWatcher(this);
refWatcher.watch(this);
}
这样,我们就能够对我们的项目进行检測。
案列
以下,我拿我们项目里面的一个内存泄露案列来解说详细的使用(前提是你的项目正确集成了LeakCanary)。
我把发生内存泄露的代码粘贴出来,也把改动后的代码粘贴出来。
发生内存泄露的代码:
在项目中。我们使用了时间总线EventBus来解耦和,我们都知道。使用EventBUs我们须要先注冊,在页面销毁的时候。我们应该先反注冊,这是因为EventBus的特定设计而成,EventBus的生命周期和整个应用的生命周期同样。
以下。我就用LeakCananry来检測因为未反注冊造成的Fragement内存泄露。
通过LeakCananry得到的Log信息例如以下:
02-17 14:40:10.219: D/LeakCanary(29354): * com.mentornow.MainActivity has leaked:
02-17 14:40:10.219: D/LeakCanary(29354): * GC ROOT static event.EventBus.defaultInstance
02-17 14:40:10.220: D/LeakCanary(29354): * references event.EventBus.typesBySubscriber
02-17 14:40:10.220: D/LeakCanary(29354): * references java.util.HashMap.table
02-17 14:40:10.220: D/LeakCanary(29354): * references array java.util.HashMapHashMapEntry[].[3]02−1714:40:10.220:D/LeakCanary(29354):∗referencesjava.util.HashMapHashMapEntry.key
02-17 14:40:10.220: D/LeakCanary(29354): * references com.mentornow.fragment.DiscoverFragment.gv
02-17 14:40:10.220: D/LeakCanary(29354): * references com.mentornow.view.MyGridView.mContext
02-17 14:40:10.220: D/LeakCanary(29354): * leaks com.mentornow.MainActivity instance
02-17 14:40:10.220: D/LeakCanary(29354): * Reference Key: fef0c426-0096-475b-9f5c-cb193fa7cecd
02-17 14:40:10.220: D/LeakCanary(29354): * Device: motorola motorola XT1079 thea_retcn_ds
02-17 14:40:10.220: D/LeakCanary(29354): * Android Version: 5.0.2 API: 21 LeakCanary:
02-17 14:40:10.220: D/LeakCanary(29354): * Durations: watch=5042ms, gc=196ms, heap dump=2361ms, analysis=26892ms
分析日志
第一句明白告诉我们MainActivity发生了内存泄露。
第二句造成内存泄露的原因是 从 EventBus的引用defaultInstance到MainActivity是可达的。
后面几句是这条GC链的节点:
EventBus首先会造成DiscoverFragment无法回收,因为DiscoverFragment保有MainActivity的引用(通过framgnet.getActivity()可得到)。所以从EventBus到MainActivity是可达的。
因为GCRoot 到MainActivity是可达的,所以GC不会回收MainActivity,从而造成内存泄露。
解决的方法
依照EventBus的使用规范,我们应该在使用完以后。进行反注冊。我们在Fragment的onDestroy方法里面调用发注冊方法,然后执行程序。
发现曾经的log不再打印。
总结
在我对公司项目排查内存泄露的时候发现,内存泄露经常让人忽略。
所以,我还是在最后总结一下会出现内存泄露的几种情形:
1。使用了Handler,而且使用了延时操作。比方轮播图
2,使用了线程。线程一般处理耗时操作,子线程部分的执行时间有可能查出页面的生命周期。假设不在线程中作处理。会发生内存泄露。解决的方法有:使用虚引用。在页面销毁时让线程终止执行等。
3,使用了匿名内部类。
因为匿名内部类保有外部类的引用,所以在Activity或者Fragment中使用匿名内部类要特别注意不要让内部类的生命周期大于外部类的生命周期。
或者使用静态内部类。
4,传入參数有误。因为项目中使用了友盟推送,对外暴露的API是UmengPushAgent这个类保有一个静态的Context,假设传入Activity,就会发生内存泄露。
等等,内存泄露非经常见,在使用LeakCanary还会检測到系统SDK的内存泄露。
为了程序健康,稳健的执行,找出并解决内存泄露问题是一个优化的方式。
Memory Leak检測神器--LeakCanary初探的更多相关文章
- LeakCanary:简单粗暴的内存泄漏检測工具
差点儿每一个程序猿在开发的过程中都会遇到内存泄漏.那么我们怎样检測到app是否哪里出现内存泄漏呢?square公司推出了一款简单粗暴的检測内存泄漏的工具-- LeakCanary 什么是内存泄漏? 内 ...
- Visual C++ 2012/2013的内存溢出检測工具
在过去,每次编写C/C++程序的时候,VLD差点儿是我的标配.有了它,就能够放心地敲代码,随时发现内存溢出. VLD最高可支持到Visual Studio 2012.不知道以后会不会支持Visual ...
- Android内存泄漏检測与MAT使用
公司相关项目须要进行内存优化.所以整理了一些分析内存泄漏的知识以及工作分析过程. 本文中不会刻意的编写一个内存泄漏的程序,然后利用工具去分析它.而是通过介绍相关概念,来分析怎样寻找内存泄漏.并附上自己 ...
- linux中内存泄漏的检測(五)记录内存泄漏的代码
到眼下为止,先后通过wrap malloc.new函数重载和计算指针内存大小的方法.基本上满足了对内存泄漏检測的须要. 假设发现了内存泄漏.那么就要找到内存泄漏的地方而且修正它了. 茫茫代码.如何去找 ...
- linux中内存泄漏的检測(一)最简单的方法
什么是内存泄漏 内存泄漏是指程序动态申请的内存在使用完后没有释放,导致这段内存不能被操作系统回收再利用. 比如这段程序,申请了4个字节的空间但没有释放,有4个字节的内存泄漏. #include < ...
- c++程序内存泄露检測工具
功能: 用于检測c++程序的内存泄露. 原理: 事实上非常easy,就是通过函数的重载机制,捕获应用程序的new, new[] , delete , delete[], malloc,calloc,f ...
- 操作系统栈溢出检測之ucosII篇
操作系统栈溢出检測之uc/osII篇 Author : David Lin (林鹏) E-mail : linpeng1 ...
- Linux C 编程内存泄露检測工具(二):memwatch
Memwatch简单介绍 在三种检測工具其中,设置最简单的算是memwatch,和dmalloc一样,它能检測未释放的内存.同一段内存被释放多次.位址存取错误及不当使用未分配之内存区域.请往http: ...
- 内存泄露检測及cvClone造成的泄露
调了几个小时,到最后发现内存泄露的原因是opencv的cvClone函数,採用cvCopy函数后,问题解决. vs2010使用vld进行内存泄露检測 (1) 下载vld工具 (2) 将D:\Progr ...
随机推荐
- http协议请求方法
解析HTTP协议六种请求方法get.put.post.delete.options和trace含义与区别. 1.GET selecr请求会向数据库发索取数据的请求,从而来获取信息,该请求就像数据库 ...
- ios中NSObject分类
#import <Foundation/Foundation.h> #define BUNDLE_PATH_IMAGENAME(c) [[NSBundle mainBundle] path ...
- Tensorflow默认占满全部GPU的全部资源
一台服务器上装了多块GPU,默认情况下启动一个深度学习训练任务时,这个任务会占满每一块GPU的几乎全部存储空间.这就导致一个服务器基本上只能执行一个任务,而实际上任务可能并不需要如此多的资源,这相当于 ...
- linux shell 脚本攻略学习13--file命令详解,diff命令详解
一.file命令详解 find命令可以通过查看文件内容来找出特定类型的文件,在UNIX/ Linux系统中,文件类型并不是由文件扩展名来决定的(windows中却正是这么做的),file命令的目的是从 ...
- Nginx防盗链的3种方法 文件防盗链 图片防盗链 视频防盗链 linux防盗链
Nginx 是一个很牛的高性能Web和反向代理服务器, 它具有有很多非常优越的特性: 在高连接并发的情况下,Nginx是Apache服务器不错的替代品,目前Web服务器调查显示Apache下降Ngni ...
- 九款命令行工具助力Linux环境下的数据分析
对于大多数熟悉了图形工作环境的朋友来说,电子表格工具无疑是第一选项.但命令行工具同样能够更快更高效地解决问题——且只须稍微学习即可上手. 大部分此类工具冻严格局限于Linux,而多数可同样运行在Uni ...
- C语言学习笔记 (002) - C++中引用和指针的区别(转载)
下面用通俗易懂的话来概述一下: 指针-对于一个类型T,T*就是指向T的指针类型,也即一个T*类型的变量能够保存一个T对象的地址,而类型T是可以加一些限定词的,如const.volatile等等.见下图 ...
- OAuth2.0实战之微信授权篇
微信开发三大坑: 微信OAuth2.0授权 微信jssdk签名 微信支付签名 本篇先搞定微信OAuth2.0授权吧! 以简书的登陆页面为例,来了解一下oauth2.0验证授权的一些背景知识: 1) 传 ...
- 【SqlServer】聚集索引与主键、非聚集索引
目录结构: contents structure [-] 聚集索引和非聚集索引的区别 聚集索引和主键的区别 主键和(非)聚集索引的常规操作 聚集索引.非聚集索引在SqlServer.MySQL.Ora ...
- html5 ajax 文件上传
http://html5demos.com/dnd-upload 看这个例子看了一会儿...这个是支持拖拽的上传. 下面代码是一个简单的ajax的文件上传: function match(url,rs ...