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

什么是内存泄露?

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

内存泄露的经典场景

1.非静态内部类的静态实例

由于内部类默认持有外部类的引用,而静态实例属于类。所以,当外部类被销毁时,内部类仍然持有外部类的引用,致使外部类无法被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。因此造成内存泄露。


2.不正确的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
            }
        }
    }

错误之处

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没撒关系了。该释放释放、该回家回家。

碎碎念

  • 当使用Cursor、File、Socket等资源时往往都使用了缓冲。在不需要的时候应该及时关闭它们,收回所占的内存空间。
  • Bitmap不用就recycle掉。注意调用recycle后并不意味着立马recycle,只是告诉虚拟机:小子,该干活咯!
  • ListView一定要使用ConvertView和ViewHolder
  • 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文件

  1. 打开DDMS视图
  2. 选中Devices下某个具体的应用程序
  3. 选中Devices下第二个小绿点Update Heap
  4. 点击Cause GC
  5. 点击Dump HPROF file
  6. 切换到MAT页卡,默认如下图所示

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值