在以前的文章Android内存泄露的几种情形中提到过在开发中常见的内存泄露问题,但是过于草率。由于刚开年,工做还没正式展开,就看了一下Github开源大户Square的LeakCanary,并用公司项目的測试环境来练手。试图找出项目中存在的内存泄露。与上一篇不一样,这一篇我会先说一下Java的内存区域以及垃圾回收机制,而后再讲LeakCanary的应用。并且会用一个在项目中遇到的真实案例来结尾。java
在对于LeakCanary来讲,咱们主要关心Java程序执行时的堆和栈。android
堆是用来存放对象的地方。栈是用来存放引用的地方。引用经过对象的句柄或者对象的地址来与对象保持关联。git
垃圾回收就发生在堆上。github
垃圾回收算法有很是多种,这里介绍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链。markdown
当一个对象从GC Root有路径可达,就说明这个对象正在被引用。app
GC对于这样的对象会“网开一面”。假设有对象没有不论什么GC Root可达。GC就会对这些对象打上标记,方便后面回收。eclipse
讲到这里有必要再介绍一下 内存泄露。当一个对象的“使命完毕”的时候,依照咱们的意愿,此时GC应该回收这部分对象的内存空间。好比:一个方法里面包括有一个局部变量A,当这种方法执行完之后。咱们但愿A很是快被回收。但是由于一些缘由没有回收。咱们就说发生了内存泄露。为何会有内存泄露?说究竟就是由于这时从GC Root到此对象是可达的。对于咱们Android来讲,Android很是多组件都有生命周期的概念,好比:Activity,Fragment。当这些组件的生命周期结束(onDestroy方法被回调)时,这些组件应该被回收掉。ide
但是由于一些缘由。比方:Activity被一个生命周期比較长的匿名内部类引用。被一个static对象引用。被Handler(一般是Handler调用了postDelay方法)引用。。。等状况。
Android对每个进程的内存占用是有限制大小的,曾经在16MB之内。这就要求咱们对内存的使用十分当心。内存泄露致使对象甚至Android组件(一般包括很是多其它引用,占用内存大)不能被回收。就会对程序安全在成极大的隐患,有可能用户在一个会引起内存泄露的动做上重复操做。使内存在很是短期内急剧膨胀,最后形成程序闪退的“悲慘结局”。然而这样的结局都不是咱们想要的,因此,咱们应该尽可能作到不让程序产生内存泄露。由于内存泄露。并不会像空指针这样的错误同样直接抛出来,普通程序猿很是难发现内存泄露带来的隐患。
据统计。94%得OOM异常都是由于内存泄露引起的。因此,解决内存泄露是咱们Android程序猿必须面对的话题。
LeakCananry是开源大户Square的一款开源产品。用于检測程序中的内存泄露。easy上手,操做简单,是广大安卓程序猿的必备神器。
GItHUB项目地址。
由于公司项目仍是在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集成完毕。
咱们需要在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.HashMap
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的内存泄露。
为了程序健康,稳健的执行,找出并解决内存泄露问题是一个优化的方式。