在分析Android内存泄漏之前,先了解一下JAVA的一些知识

1. JAVA中的对象的创建

  • 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象

    垃圾回收器回收非存活的对象,并释放对应的内存空间

2.Java如何判定对象存活或死亡?

  • 引用计数法

    1给对象中添加一个引用计数,假如为count

    2当引用这个对象时:count++

    3当count==0时:对象处于,也就是说没有其它地方在引用这个对象了,对象就处于“死亡”状态,回收对象

  • 可达性分析算法

    举个例子:像找人一样,A认识B,B认识C,C认识D,那么A就要吧通过这样的关系认识D,如果能找到D,说明D对象是存活的,不能回收,如果通过所有的关系都找不到D,说明D是“死亡”的,回收D对象。

    可达性分析算法的定义:通过一系列的称为 GC

    Roots 的对象作为起点,从这些节点开始向下搜索,搜索把走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连(就是从GC Roots 到这个对象不可达)时,则证明此对象是不可用的。如下图,Object5,Object6,Object7就是不可达对象,是要被回收的对象

问:哪些对象可以作为GC Roots对象呢?

  • 1虚拟机栈中引用的对象
  • 2方法区中类静态属性引用的对象
  • 3方法区中常量引用的对象
  • 4本地方法栈中JNI引用的对象

3.引用分类

  • 强引用:只要强引用还存在,垃圾回收器永远不回收强引用的对象.如下
Object obj = new Object()  //强引用
  • 软引用:在内存溢出异常之前,回收对象
String str=new String("123");   // 强引用
SoftReference softRef=new SoftReference(str); // 软引用
  • 弱引用 : 在下一次 GC 时,无论当前内存是否足够,都会回收被引用的对象
String str=new String("abc");
WeakReference abcWeakRef = new WeakReference(str);
str=null;
  • 虚引用:虚引用 : 没用,形同虚设,唯一的用处是在对象回收时,会收到一个系统通知

注:JAVA中这4种引用的级别由高到低依次为: 强引用 > 软引用 > 弱引用 > 虚引用

** 4.JAVA中内存分配 **

  • 静态储存区:编译时就分配好,在程序整个运行期间都存在。它主要存放静态数据和常量
  • 栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存
  • 堆区:通常存放 new 出来的对象。由 Java 垃圾回收器回收。

上面的是JAVA的一些预备知识,下面分析Android内存泄漏相关

** 1 内存泄漏与内存溢出**

  • 内存泄漏:Memory Leak , 无用的对象应该被回收的没有被回收
  • 内存溢出:常说的OOM,没有足够的内存供分配了

** 2 Android内存泄漏分类 **

  • 长期持有(Activity)Context导致的

    (1) 单例类持有Activity引用

    (2) 长生命周期引用短生命周期

  • 由非静态内部类或者匿名内部类导致的

    (1) Handler泄漏

  • 资源使用完忘记释放

    (1) Cursor,InputStream/OutputStream 忘记调用close

  • 使用某些系统服务不当

    (1) 在6.0系统,获取ConnectivityManager服务,如果第一次使用的是Activity对应的Context去获取这个服务,就会导致内存泄漏

  • 延迟的任务也可能导致内存泄漏

    (1) Handler 的消息未处理完,这时如果Handler是在Activity内存类实现的,消息引用Handler,Handler又引用了Activity,这时如果关闭Activity,就会造成内存泄漏

  • 忘记注销监听器或者观察者

    (1) 比如 EventBus.unregister() 忘记调用

注:非静态内部类和匿名内部类都会潜在的引用它们所属的外部类,但是静态内部类却不会

** 3 Android内存泄漏分析工具 **

  • MAT
  • LeakCanary
  • Strictmode
  • Android Memory Monitors

推荐使用LeakCanary,•LeakCanary是一个检测Java和Android内存泄漏的库,集成LeakCanary之后,只需要等待内存泄漏出现就可以了无需认为进行主动检测

** 4 LeakCanary的添加 **

  • 第一步:在build.gradle中添加依赖

    compile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
  • 第二步:在Application的onCreate()方法中添加 LeakCanary.install(this);

完成以上两步,就添加了LeakCanary,接下来就正常开发测试就行了,如果有内存泄漏,就会在通知栏中会有相应的通知,点开看就可以了,找到对应的内存泄漏的地方,解决

下面是演示的内存泄漏的几张图,可以看一下:

5 Android内存泄漏的案例

  • 案例一:单例造成的内存泄漏

    典型比如context的使用不当造成内存泄漏
public class ToastUtils {
private static String oldMsg;
protected static Toast toast = null;
private static long oneTime = 0;
private static long twoTime = 0;
private static long gapTime = 3 * 1000;//3s只显示一次 public static void show(Context context, String s) {
if (context != null && !TextUtils.isEmpty(s)) {
if (toast == null) {
toast = Toast.makeText(context, s, Toast.LENGTH_SHORT);
toast.show();
oneTime = System.currentTimeMillis();
} else {
twoTime = System.currentTimeMillis();
if (s.equals(oldMsg)) {
if (twoTime - oneTime > gapTime) {
toast.show();
}
} else {
oldMsg = s;
toast.setText(s);
toast.show();
}
}
oneTime = twoTime;
}
}
}

在Activity 中使用:

ToastUtils.show(this, "登录成功");

上面的代码就会出现内存泄漏,因为在activity中使用ToastUtils.show(this, "登录成功")的时候,传的第一个参数 this 代表当时的activity,而ToashTuils中的toast变量是一个静态变量,

代码如下

 protected static Toast toast = null;

创建toast对象如下代码

toast = Toast.makeText(context, s, Toast.LENGTH_SHORT);

Toast.makeText的第一参数就是上面传的activity,Toast类中有一个变量mContext会保存这个activity,就是强引用,但是toast又是一个静态的变量,静态变量的生命同期是和当前的APP的进程一样长的,所以这时我们如果关闭这个Activity,就会导致Activity被静态变量强引用,垃圾回收永远不会回收这个Activity,所以就会出现内存泄漏。

我们看一下Toast.makeText的源码

上面图中,new一个Toast,把context传给了Toast的构造方法。

所以调用 ToastUtils.show(this, "登录成功");就会导致 activity 被静态的toast变量强引用了,导致内存泄漏。

解决方法

用ApplicationContext替代Activity,如下代码

 public static void show(Context context, String s) {
//在这里获取applicationContext,applicationContext的生命周期是和进程一样长
//这样就不会出现内存泄漏了
context = context.getApplicationContext(); if (context != null && !TextUtils.isEmpty(s)) {
if (toast == null) {
toast = Toast.makeText(context, s, Toast.LENGTH_SHORT);
toast.show();
......
}
}
  • 案例二 内部类或者匿名内部类造成的内存泄漏

    比如在Activity中使用Handler不当造成的内存泄漏

    如下图

上图:在MainActivity中有一个匿名内部类Handler,并且有一个此类的对象 uiHandler。

这时我们如果在MainAcitity 中调用下面代码,就会出现内存泄漏

uiHandle.sendMessageDelayed(uiHandle.obtainMessage(),60 * 1000);

uiHandler.obtainMessage获取的msg 中有一个成员变量 target,target保存的就是uiHandle,而uiHandler又是内部类创建的对象,所以uiHandler隐式的会对当前的外部类,也就是MainActivity会有一个强引用,如下

msg -> uiHandler -> MainActivity

msg 引用了uiHandler,uiHandler引用了MainActivity,然后这个msg需要60s后才被处理完,在处理过程中,如果退出MainActivity,这时候就会导致内存泄漏,MainActivity回收不了。应该被回收的对象没有被回收掉,就是内存泄漏。

注:handler机制不明白的可以先看下handler机制,message,handler,loop的关系

解决方法

  • 可以在MainActivity的onDestroy()方法调用下面代码:
uiHandle.removeCallbacksAndMessages(null);
  • Handler不要用内部类,用静态的内部类,因为静态的内部类不会引用外部类,需要外部类的地方,用弱引用,代码如下:

使用弱引用的时候,需要作一下判断是否为null。

  • 案例三:Activity context的不正确使用

    上面的两个案例中其实也是context的使用场景不当造成的内存泄漏,这里不再举例,我们通常使用的两种context是 Acitivty和 Application,只需要注意对context的使用不要超过它的生命同期。部分情况下可以使用applicationContext代替activity的context,因为applicatoinContext会随着应用程序的存在而存在,而不依赖于activity的生命周期。还有要慎重对context使用static关键字。

  • 案例四:一些资源使用完后没有关闭

    如数据库的游标 Cursor,输入输出流 InputStream/OutputStream没有close

  • 案例五:注册的监听器没有反注册

    如EventBus.register,ButterKnife等没有在activity的onDestroy中反注册或者其它地方反注册

  • 案例六:系统服务的泄漏

    在实际项目中发现的,在6.0系统上在activity中第一次如果用的是activity对应的context获取ConnectivityManager服务会造成内存泄漏。

    代码对下:

     /**
* 判断是否有网络连接
* @param context
* @return
*/
public static boolean isNetworkConnected(Context context) {
if (context != null) {
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mNetworkInfo = cm.getActiveNetworkInfo();
if (mNetworkInfo != null) {
return mNetworkInfo.isAvailable();
}
}
return false;
}

如果是第一次在activity中调用如下代码,会发现内存泄漏

//注意,这个this 是代表的是当前的activity
isNetworkConnected(this)

简单介绍下:先从Context的getSystemService方法开始,我们知道Activity是从ContextWrapper继承而来的,ContextWrapper中持有一个mBase实例,这个实例指向一个ContextImpl对象,同时ContextImpl对象持有一个OuterContext对象,对于Activity来说,这个OuterContext就是Activity对象。所以调用getSystemService最终会调用到ContextImpl的getSystemService方法。

在6.0上,在6.0上,ConnectivityManager实现为单例,创建这个单例对象的时候,把相应的OuterContext就是Activity对象,保存到了ConnectivityManager中,就造成了一个单例对象强引用了activity对象,从而造成了内存泄漏,如果是第一次用的是application,则保存的不是activity而是application,反而不会出现内存泄漏了。

使用LeakCanary检测 ConnectivityManager 内存泄漏图如下:

解决方法

使用applicationContext去获取服务,不要使用activityContext去获取服务

上面的就是对Android内存泄漏的一些总结,如果有不正确的或者需要补充的地方,请指出,一块学习进步

Android 内存泄漏分析与解决方法的更多相关文章

  1. Android开发之漫漫长途 番外篇——内存泄漏分析与解决

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. Android内存泄漏分析及调试

    尊重原创作者,转载请注明出处: http://blog.csdn.net/gemmem/article/details/13017999 此文承接我的另一篇文章:Android进程的内存管理分析 首先 ...

  3. 上Mysql com.mysql.jdbc.StatementImpl$CancelTask内存泄漏问题和解决方法

    近来在负责公司短信网关的维护及建设,随着公司业务发展对短信依赖越来越严重了,短信每天发送量也比曾经每天40多w发送量暴增到每天达到200w发送量.由于是採用Java做发送底层,压力递增情况下不可避免的 ...

  4. Android内存泄漏分析实战

    内存泄漏简单介绍 java能够保证当没有引用指向对象的时候,对象会被垃圾回收器回收.与c语言自己申请的内存自己释放相比,java程序猿轻松了非常多.可是并不代表java程序猿不用操心内存泄漏.当jav ...

  5. (转)Android内存泄漏分析及调试

      http://blog.csdn.net/gemmem/article/details/13017999 此文承接我的另一篇文章:Android进程的内存管理分析  首先了解一下dalvik的Ga ...

  6. Android handler 内存泄露分析及解决方法

    1. 什么是内存泄露? Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引 ...

  7. android 内存泄漏分析技巧

    java虚拟机执行一般都有一个内存界限,超过这个界限,就会报outofmemory.这个时候一般都是存在内存泄漏.解决内存泄漏问题,窃以为分为两个步骤:分析应用程序是否真的有内存泄漏,找到内存泄漏的地 ...

  8. android 内存泄漏,以及检测方法

    1.为什么会产生内存泄漏 当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏. 2.内 ...

  9. Android内存泄漏分析

    周末去上海参加了安卓巴士组织的技术论坛,去了才发现自己基础很渣..... 其中提到了android的内存泄漏的问题,回来马上度娘(虽说度娘很渣),整理如下: 一.单例造成的内存泄漏 因为单例的静态特性 ...

随机推荐

  1. Android中的socket本地通讯框架

    一.先分析Native层: 1.C++基类SocketListener: class SocketListener {     int mSock;     const char *mSocketNa ...

  2. CSS 预处理器中的循环

    本文由 nzbin 翻译,黄利民 校稿.未经许可,禁止转载! 英文出处:css-tricks.com 发表地址:http://web.jobbole.com/91016/ 如果你看过老的科幻电影,你一 ...

  3. mui 页面间传值得2种方式

    通过最近得工作开发刚接触mui框架,用到了页面间得传值, 第一种:通过url进行传值 父页面代码: mui.openWindow({ id:'子页面.html', url:'子页面.html?para ...

  4. redis 压缩链表

    redis 压缩链表 概述 压缩链表是相对于普通链表而言的 当普通链表的数据越来越多, 链表查询性能会低效 当存储的数据较少时, 使用链表存储会浪费空间 压缩链表本质上是一个字符串 压缩链表内存储的数 ...

  5. js 解析本地Excel文件!

    通常,一般读取Excel都是由后台来处理,不过如果需求要前台来处理,也是可以的.. 1.需要用到js-xlsx,下载地址:js-xlsx 2.demo: <!DOCTYPE html>&l ...

  6. JS自定义对象,正则表达式,JQuery中的一些知识点

    一:自定义对象 1.基本概念:①对象:包含一系列无序属性和方法的集合.②键值对:对象中的数据是以键值对的形式存在的,以键取值.③属性:描述对象特征的一系列变量.[对象中的变量]④方法:描述对象行为的一 ...

  7. 04(1) 基于上下文相关的GMM-HMM声学模型1

    1.上下文对音素发音的语谱轨迹的影响 受到上下文的影响,同一个音素的发音语谱轨迹不同 为提高识别准确率,对音素建模时应将这种上下文影响考虑在内 2.基于上下文相关的音素建模 注意,非单音素建模中,每个 ...

  8. DELL Precision Tower7910重装系统+开机出现GRUB界面如何处理

    想给实验室的工作站重新装个Win7系统,因为以前并没装过工作站的系统,发现和普通的电脑装系统还是有些不一样的.主要的问题就在于主板的不同. 尝试了老毛桃U盘启动盘安装,结果在WinPE里面提示找不到硬 ...

  9. bzoj3112 [Zjoi2013]防守战线

    正解:线性规划. 直接套单纯形的板子,因为所约束条件都是>=号,且目标函数为最小值,所以考虑对偶转换,转置一下原矩阵就好了. //It is made by wfj_2048~ #include ...

  10. Laravel 5.2 教程 - 文件上传

    一.简介 Laravel 有很棒的文件系统抽象层,是基于 Frank de Jonge 的 Flysystem 扩展包. Laravel 集成的 Flysystem 提供了简单的接口,可以操作本地端空 ...