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 ...
随机推荐
- centos nginx+php+mysql 安装libiconv不成功
wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.13.1.tar.gz tar -zxvf libiconv-1.13.1.tar.gzcd l ...
- ftp相关常用命令
工欲善其事必先利其器 00.FTP常用命令 01.SFTP命令 sftp user@ip:/tmp
- 阿里云安装jdk,tomcat,maven,svn,git,nginx
1. 首先通过xftp等工具上传安装包 2. 配置目录 cd usr mkdir java cd java mkdir jdk mkdir tomcatmkdir maven 3. 安装jdk 3.1 ...
- CentOS设置开机自动启动某服务
CentOS设置开机自动启动某服务 这里以启动sshd服务为例: 查看sshd是否已经是系统服务: # chkconfig --list |grep sshd 会显示: sshd ...
- Hopfield神经网络和TSP问题
一.TSP问题 旅行商问题,又叫货郎担问题.它是指如下问题:在完全图中寻找一条最短的哈密尔顿回路. 哈密尔顿回路问题:给定一个图,判断图中是否存在哈密尔顿回路. 哈密尔顿回路:寻找一条回路,经过图中所 ...
- java struts2入门学习---拦截器学习
一.拦截器,拦截器栈 1.拦截器的作用 拦截器本质上和servlet的过滤器是一样的.在struts2中,拦截器能够对Action前后进行拦截,拦截器是一个可插拨的,你可以选择使用拦截器,也可以卸载拦 ...
- string与char*的转换方法
c_str函数的返回值是const char*的,不能直接赋值给char*,所以就需要我们进行相应的操作转化,下面就是这一转化过程. c++语言提供了两种字符串实现,其中较原始的一种只是字符串的c语言 ...
- HDU 3951 Coin Game (简单博弈)
Coin Game Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total S ...
- MVC+Spring.NET+NHibernate .NET SSH框架整合 C# 委托异步 和 async /await 两种实现的异步 如何消除点击按钮时周围出现的白线? Linq中 AsQueryable(), AsEnumerable()和ToList()的区别和用法
MVC+Spring.NET+NHibernate .NET SSH框架整合 在JAVA中,SSH框架可谓是无人不晓,就和.NET中的MVC框架一样普及.作为一个初学者,可以感受到.NET出了MV ...
- numpy的生成网格矩阵 meshgrid()
numpy模块中的meshgrid函数用来生成网格矩阵,最简单的网格矩阵为二维矩阵 meshgrid函数可以接受 x1, x2,..., xn 等 n 个一维向量,生成 N-D 矩阵. 1 基本语法 ...