在之前的文章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初探的更多相关文章

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

    差点儿每一个程序猿在开发的过程中都会遇到内存泄漏.那么我们怎样检測到app是否哪里出现内存泄漏呢?square公司推出了一款简单粗暴的检測内存泄漏的工具-- LeakCanary 什么是内存泄漏? 内 ...

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

    在过去,每次编写C/C++程序的时候,VLD差点儿是我的标配.有了它,就能够放心地敲代码,随时发现内存溢出. VLD最高可支持到Visual Studio 2012.不知道以后会不会支持Visual ...

  3. Android内存泄漏检測与MAT使用

    公司相关项目须要进行内存优化.所以整理了一些分析内存泄漏的知识以及工作分析过程. 本文中不会刻意的编写一个内存泄漏的程序,然后利用工具去分析它.而是通过介绍相关概念,来分析怎样寻找内存泄漏.并附上自己 ...

  4. linux中内存泄漏的检測(五)记录内存泄漏的代码

    到眼下为止,先后通过wrap malloc.new函数重载和计算指针内存大小的方法.基本上满足了对内存泄漏检測的须要. 假设发现了内存泄漏.那么就要找到内存泄漏的地方而且修正它了. 茫茫代码.如何去找 ...

  5. linux中内存泄漏的检測(一)最简单的方法

    什么是内存泄漏 内存泄漏是指程序动态申请的内存在使用完后没有释放,导致这段内存不能被操作系统回收再利用. 比如这段程序,申请了4个字节的空间但没有释放,有4个字节的内存泄漏. #include < ...

  6. c++程序内存泄露检測工具

    功能: 用于检測c++程序的内存泄露. 原理: 事实上非常easy,就是通过函数的重载机制,捕获应用程序的new, new[] , delete , delete[], malloc,calloc,f ...

  7. 操作系统栈溢出检測之ucosII篇

    操作系统栈溢出检測之uc/osII篇 Author               :       David Lin (林鹏) E-mail               :       linpeng1 ...

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

    Memwatch简单介绍 在三种检測工具其中,设置最简单的算是memwatch,和dmalloc一样,它能检測未释放的内存.同一段内存被释放多次.位址存取错误及不当使用未分配之内存区域.请往http: ...

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

    调了几个小时,到最后发现内存泄露的原因是opencv的cvClone函数,採用cvCopy函数后,问题解决. vs2010使用vld进行内存泄露检測 (1) 下载vld工具 (2) 将D:\Progr ...

随机推荐

  1. RRT路径规划算法

    传统的路径规划算法有人工势场法.模糊规则法.遗传算法.神经网络.模拟退火算法.蚁群优化算法等.但这些方法都需要在一个确定的空间内对障碍物进行建模,计算复杂度与机器人自由度呈指数关系,不适合解决多自由度 ...

  2. 粒子滤波跟踪移动机器人(MATLAB Robotics System Toolbox)

    MathWorks从MATLAB 2015a开始推出与ROS集成的Robotics System Toolbox(机器人系统工具箱),它为自主移动机器人的研发提供现成的算法和硬件接口. 粒子滤波基本流 ...

  3. VTK三维点集轮廓凸包提取

    碰撞检测问题在虚拟现实.计算机辅助设计与制造.游戏及机器人等领域有着广泛的应用,甚至成为关键技术.而包围盒算法是进行碰撞干涉初步检测的重要方法之一.包围盒算法是一种求解离散点集最优包围空间的方法.基本 ...

  4. in文件

    in文件: 被调用的配置文件

  5. 编译安装imagick出错:make: *** [imagick_class.lo] Error 1

    /usr/local/lnmpsrc/imagick-3.0.1/imagick_class.c:9673: warning: assignment makes pointer from intege ...

  6. Centos7新装配置, 并使用openvpn client长连接远程备份

    1. 修改本机hostname // 查看本机hostname hostnamectl //永久性的修改主机名称, 修改完后新开的terminal中立刻生效. 也可以直接修改 /etc/hostnam ...

  7. ios用户登录记住密码

    登录 记录已登录用户步骤,存入偏好设置中存储放入一个数组. 具体存储 :存储用户到偏好设置中,其中用户是一个数组 向服务器响应客户端后的一些操作 (如果响应数据成功)其中用户和密码是一一对应的 .1先 ...

  8. java读取txt字符串挨个写入int数组

    int []num=new int[1001]; FileReader fr = new FileReader("1.txt"); BufferedReader br = new ...

  9. std::bind 详解及参数解析

    // Bind_std_function.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> ...

  10. HDUOJ-----A == B ?

    A == B ? Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total S ...