Android 内存泄露简介、典型情景及检测解决

转载 2016年05月31日 15:43:21

什么是内存泄露?

Android虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,导致不能及时回收这个对象所占用的内存。内存泄露积累超过Dalvik堆大小,就会发生OOM(OutOfMemory)。

内存泄露的经典场景

非静态内部类的静态实例

由于内部类默认持有外部类的引用,而静态实例属于类。所以,当外部类被销毁时,内部类仍然持有外部类的引用,致使外部类无法被GC回收。因此造成内存泄露。

举个栗子

    private static Leak mLeak;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mLeak = new Leak();
    }

    class Leak {
    }

错误栗子说明:static关键字修饰mLeak属性,将mLeak存在静态区中,而Leak为内部类,默认持有外部类的引用。当Activity销毁时,mLeak紧紧抱住Activity的大腿深情告白:“MLGB!劳资就是不放你走!”。斗不过mLeak属性的GC,自然不敢回收二手娘们Activity。因此造成内存泄露。

不正确的Handler

错误代码示例:

    private MyHandler mMyHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mMyHandler = new MyHandler();
        mMyHandler.sendMessageDelayed(new Message(), 10 * 1000);
    }

    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

正确写法如下:

    private MyHandler mMyHandler;
    static class MyHandler extends Handler {
        WeakReference<Activity> mActivityWeak;

        MyHandler(Activity act) {
            mActivityWeak = new WeakReference<Activity>(act);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mActivityWeak.get() != null) {
                // doSomething
            }
        }
    }

我们知道在handler.sendMessage(msg)时,msg.target会指向handlermsg会插入MessageQueue。此为下面讲解的基础,对这部分不太熟悉的同学可以参考这篇博客。

错误之处

MyHandler为内部类,默认持有外部类的引用。当Activity销毁时,如果MessageQueue中仍有未处理的消息,那么mMyHandler示例将继续存在。而mMyHandler持有Activity的引用。故Activity无法被GC回收。

正确解析

static关键字修饰MyHandler类,使MyHandler不持有外部类的引用。使用WeakReference<activity>保证当
activity销毁后,不耽误gc回收activity占用的内存空间,同时在没被销毁前,可以引用activity

管它正确错误都让它正确

通过上面的分析,可以得出结论:Handler造成内存泄露时,是因为MessageQueue中还有待处理的Message,那我们在Activity#onDestroy()中移除所有的消息不完事了嘛。反正Activity都销毁了,MessageQueue中的msg也就什么存在的意义了,可以移除。代码如下:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除所有的callback和msg
        mMyHandler.removeCallbacksAndMessages(null);
    }

静态变量引起内存泄露

这里以单例模式引起Context泄露为例

public class Singleton {
    private static Singleton instance;
    private Singleton(Context context){
    }

    public static Singleton getInstance(Context context){
        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton(context);
                }
            }
        }
        return instance;
    }
}

错误之处

在调用Singleton#getInstance()方法如果传入了Activity。如果instance没有释放,那么这个Activity将一直存在。因此造成内存泄露。

修正版

new Singleton(context)改为new Singleton(context.getApplicationContext())即可,这样便和传入的Activity没撒关系了。该释放释放、该回家回家。

碎碎念

  • 当使用CursorFileSocket等资源时往往都使用了缓冲。在不需要的时候应该及时关闭它们,收回所占的内存空间。
  • Bitmap不用就recycle掉。注意调用recycle后并不意味着立马recycle,只是告诉虚拟机:小子,该干活咯!
  • ListView一定要使用ConvertViewViewHolder
  • BraodcastReceiver注册完事,不用时也要反注册

    内存泄露的检测

    Heap工具

  1. 打开DDMS视图
  2. 选中Devices下某个具体的应用程序
  3. 选中Devices下第二个小绿点Update Heap
  4. 不断运行程序并点击Cause GC
  5. 关注data Object行、Toal Size列
  6. 耍你的APP去吧,如果发现Toal Size越来越大,很可能有内存泄露的发生

    MAT(Memory Analyzer Tool)工具

    导出.hprof文件

  7. 打开DDMS视图
  8. 选中Devices下某个具体的应用程序
  9. 选中Devices下第二个小绿点Update Heap
  10. 点击Cause GC
  11. 点击Dump HPROF file
  12. 切换到MAT页卡,默认如下图所示


最显眼的就是饼图了,里面列出了每种类型的数据所占大小。和红色箭头所指的Dominator有的一拼,然而这并没有什么卵用。我们的重点在Histogram。没撒说的,点击它。默认图如下


默认是按Class排序,第一行支持正则表达式。为了查看方便,下面我们会以Group by package的形式分组。正确的打开方式应该是这个样子的。


内存泄露Demo

这里以非静态内部类的静态实例为例,Demo只有两个Activity,MainActivity中只有一个按钮,点击跳转到SecondActivity

public class SecondActivity extends Activity {

    private static Leak mLeak;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mLeak = new Leak();
    }

    class Leak {

    }
}

查找内存泄露

启动APP,点击进入SecondActivity,然后按back键返回到MainActivity。打开.hprof文件。查找我们的包名com.dyk.memoryleak


可以看到,虽然我们结束了SecondActivity,但是SecondActivity仍然存在,内存泄露无疑。

1.右键SecondActivity,选择List Objects—→with incoming references


结果如下图:


2.右键com.dyk.memoryleak.SecondActivity,选择Path to GC—→with all references


结果如下图:


可以看到是因为mLeak属性的引用导致SecondActivity无法回收。既然找到了内存泄露的原因,通过上文的介绍,相信改起来难度应该不是很大的。

3.再次进入SecondActivity。由于上次创建的SecondActivity还没有被回收,可以预期到此时应该存在两个SecondActivity实例。


关于内存泄露的内容暂时到此为止了。MAT更多的功能,请自行查找学习。感谢耐心阅读到最后~




原文:http://blog.csdn.net/qq_17250009/article/details/51261616

Android内存泄露自动检测神器LeakCanary

经典的面试题: a、怎样在coding中避免内存泄露? b、怎样检测内存泄露? 这两个问题我想大部分android 职位面试时都会被问到吧。         怎样避免就不赘述了,网上很多答案。    ...

Android 内存泄露原理和检测

Android进程的内存管理分析

Android内存泄露分析(MemoryAnalyzer工具)

前提条件: 1,电脑安装了java 运行环境   2,手机端开启了 USB 调试开关  3,获取 root 权限 基本步骤: 1,使用eclipse 自带的 DDMS 工具分析各线程的内存使...

Android内存泄漏分析及调试

首先了解一下dalvik的Garbage Collection:     如上图所示,GC会选择一些它了解还存活的对象作为内存遍历的根节点(GC Roots),比方说thread stack中的...
  • gemmem
  • gemmem
  • 2013年10月25日 11:31
  • 37320

基于Android Studio的内存泄漏检测与解决全攻略

自从Google在2013年发布了Android Studio后,Android Studio凭借着自己良好的内存优化,酷炫的UI主题,强大的自动补全提示以及Gradle的编译支持正逐步取代Eclip...

Android 内存泄漏总结(超级实用)

Android 内存泄漏总结内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不...

Android内存泄漏的常见场景及解决方案

哲学老师说,看待事物无非是了解它是什么,为什么,怎么做 所以,首先,我们先了解一下什么是“内存泄漏”   摘自百度的一段话:用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据...

Android内存优化-内存泄漏的几个场景以及解决方式

一.什么是内存泄漏 在Java程序中,如果一个对象没有利用价值了,正常情况下gc是会对其进行回收的,但是此时仍然有其他引用指向这个活在堆内存中的对象,那么gc就不会认为这个对象是一个垃圾,那么就不会...

http协议post数据标准格式

在C#中有HttpWebRequest类,可以很方便用来获取http请求,但是这个类对Post方式没有提供一个很方便的方法来获取数据。网上有很多人提供了解决方法,但都参差不齐,这里我把我使用的方法总结...

JAVA对象引用和值引用

以前就知道JAVA对象分对象引用和值引用,并且还知道8种基础数据类型,即引用时是值引用的数据类型,比如int,short,long,byte,float,double,char,boolean,其它都...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android 内存泄露简介、典型情景及检测解决
举报原因:
原因补充:

(最多只允许输入30个字)