前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 内存泄露简介、典型情景及检测解决

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

作者头像
非著名程序员
发布2018-02-02 13:49:43
7470
发布2018-02-02 13:49:43
举报
文章被收录于专栏:非著名程序员非著名程序员

什么是内存泄露?

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

内存泄露的经典场景

非静态内部类的静态实例

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

举个栗子

代码语言:javascript
复制
    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

错误代码示例:

代码语言:javascript
复制
    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);
        }
    }

正确写法如下:

代码语言:javascript
复制
    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也就什么存在的意义了,可以移除。代码如下:

代码语言:javascript
复制
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除所有的callback和msg
        mMyHandler.removeCallbacksAndMessages(null);
    }

静态变量引起内存泄露

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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更多的功能,请自行查找学习。感谢耐心阅读到最后~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-05-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 非著名程序员 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是内存泄露?
  • 内存泄露的经典场景
    • 非静态内部类的静态实例
      • 举个栗子
    • 不正确的Handler
      • 错误之处
      • 正确解析
      • 管它正确错误都让它正确
    • 静态变量引起内存泄露
      • 错误之处
      • 修正版
    • 碎碎念
      • 内存泄露Demo
      • 查找内存泄露
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档