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的内存泄露。

为了程序健康,稳健的执行,找出并解决内存泄露问题是一个优化的方式。

时间: 2024-10-07 05:16:11

Memory Leak检測神器--LeakCanary初探的相关文章

Memory Leak检测神器--LeakCanary初探

??在之前的文章Android内存泄露的几种情形中提到过在开发中常见的内存泄露问题,但是过于草率.由于刚开年,工作还没正式展开,就看了一下Github开源大户Square的LeakCanary,并用公司项目的测试环境来练手,试图找出项目中存在的内存泄露.与上一篇不同,这一篇我会先说一下Java的内存区域以及垃圾回收机制,然后再讲LeakCanary的应用,并且会用一个在项目中遇到的真实案例来结尾. Java的内存模型 ??在对于LeakCanary来说,我们主要关心Java程序运行时的堆和栈.堆

LeakCanary:简单粗暴的内存泄漏检測工具

差点儿每一个程序猿在开发的过程中都会遇到内存泄漏.那么我们怎样检測到app是否哪里出现内存泄漏呢?square公司推出了一款简单粗暴的检測内存泄漏的工具-- LeakCanary 什么是内存泄漏? 内存泄漏是指因为疏忽或者错误造成程序未能释放已经不再使用的内存,内存泄漏不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误失去了对于这段内存的控制.因而造成内存的浪费. 内存泄漏和内存溢出是两码事,不要混淆,内存溢出通俗的讲就是内存不够用,如今的仅仅能手机内存越来越大,内存溢出的情况不

Visual C++ 2012/2013的内存溢出检測工具

在过去,每次编写C/C++程序的时候,VLD差点儿是我的标配.有了它,就能够放心地敲代码,随时发现内存溢出. VLD最高可支持到Visual Studio 2012.不知道以后会不会支持Visual Studio 2013,但反正眼下是不支持的. 相关的讨论见:https://vld.codeplex.com/discussions/471214 那么在Visual Studio 2013下还是老老实有用MFC的内存溢出检測工具,或者用WinDBG吧. 推荐一篇文章:<Memory Leak D

【OpenCV新手教程之十七】OpenCV重映射 &amp;amp; SURF特征点检測合辑

本系列文章由@浅墨_毛星云 出品.转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/30974513 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 知乎:http://www.zhihu.com/people/mao-xing-yun 邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本号: 2.4.9 本篇文章中,我们一起探讨了OpenCV

Linux C 编程内存泄露检測工具(二):memwatch

Memwatch简单介绍 在三种检測工具其中,设置最简单的算是memwatch,和dmalloc一样,它能检測未释放的内存.同一段内存被释放多次.位址存取错误及不当使用未分配之内存区域.请往http://www.linkdata.se/sourcecode.html下载最新版本号的Memwatch. 安装及使用memwatch 非常幸运地,memwatch根本是不须要安装的,由于它仅仅是一组C程序代码,仅仅要在你程序中添?memwatch.h,编译时加上-DMEMWATCH -DMW_STDIO

内存泄露检測及cvClone造成的泄露

调了几个小时,到最后发现内存泄露的原因是opencv的cvClone函数,採用cvCopy函数后,问题解决. vs2010使用vld进行内存泄露检測 (1) 下载vld工具 (2) 将D:\Program Files\Visual Leak Detector\include;D:\Program Files\Visual Leak Detector\lib\Win32;分别增加include和lib的路径 (3) 将D:\Program Files\Visual Leak Detector\bi

ASP怎样检測某目录是否存在,不存在则自己主动创建

ASP怎样检測某目录是否存在,不存在则自己主动创建 folder=server.mappath("/imagess") Set fso = CreateObject("Scripting.FileSystemObject") if fso.fileexists(Server.mappath(filepath)) then respnse.write("都有了还建什么建") else fso.createfolder(folder) end if

模式识别 - 有害视频检測程序的策略

有害视频检測程序的策略 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26346831 有害(色情\恐怖\暴力)视频, 严重危害网络的健康, 须要进行检測和过滤. 检測色情\恐怖视频, 通过检測程序, 检測出多个场景的概率, 然后进行排序, 当场景多余6个时, 仅仅取最大的6个场景; 返回的概率值是前3个最大检測值场景的概率的均值; 色情\恐怖汇总时, 首先检測色情, 假设为色情视频, 则不进行恐怖的检測, 否则继续检測恐怖,

图像边缘检測小结

边缘是图像中灰度发生急剧变化的区域边界. 图像灰度的变化情况能够用图像灰度分布的梯度来表示,数字图像中求导是利用差分近似微分来进行的,实际上经常使用空域微分算子通过卷积来完毕. 一阶导数算子 1)  Roberts算子 Roberts算子是一种斜向偏差分的梯度计算方法.梯度的大小代表边缘的强度.梯度的方向与边缘的走向垂直.Roberts操作实际上是求旋转 \pm" title="\pm" >45度两个方向上微分值的和. Roberts算子定位精度高,在水平和垂直方向的效