Android常见内存泄漏总结

1,915 阅读5分钟

内存泄漏原理
真的很详细, 获益匪浅

博客原文
Java内存泄漏的根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。

知识点:
1, new的对象成员变量中, 这部分内存在不使用时由GC来回收
2, 方法体的局部变量中,方法执行完,则局部变量持有的内存会被自动释放,也就是不再持有对象的引用
3, 当一个对象的任务已经执行完毕,正常变量名的引用已经结束, 但是其他还在执行的对象, 还间接持有该对象, 导致内存泄漏
4, 判断对象是否可达

avatar

不可达

何为不可达
(1)改变对象的引用,如置为null或者指向其他对象。
Object x=new Object();//object1
Object y=new Object();//object2
x=y;//object1 变为垃圾
x=y=null;//object2 变为垃圾
(2)超出作用域
if(i==0){
Object x=new Object();//object1
}
//括号结束后object1将无法被引用,变为垃圾
(3)类嵌套导致未完全释放 class A{ A a; } A x= new A();//分配一个空间 x.a= new A();//又分配了一个空间 x=null;//将会产生两个垃圾
(4)线程中的垃圾 class A implements Runnable{
void run(){ //.... }
}
//main
A x=new A();//object1
x.start();
x=null;
//等线程执行完后object1才被认定为垃圾
这样看,确实在代码执行过程中会产生很多垃圾,不过不用担心,java可以有效地处理他们。

5, 内存泄漏示意图

avatar

1, 非静态内部类

class Activity{
    r = new Runnable(){
        run(){//耗时操作}
    }
    t = new Thread(){}
}

当退出activity, 但耗时操作还未结束时, 就会内存泄漏, 因为new接口在java语法里, 就是创建匿名内部类的意思, java语法里接口是不能new的, 而非静态内部类会持有外部类, 所以Activity类虽然已经结束, 理论应该被GC回收, 但是还在被Runnable持有, 导致内存泄漏

解决方法:

class Activity{
    MyRunnable r = new MyRunnable()
    void create()
    private static class MyThread extends Thread {
    }
    private static class MyRunnable implements Runnable{
    }
}

改成静态内部类, 因为静态内部类, 并不在静态区, 创建出来后就是一个普通的对象, 生命周期跟随普通类

2, Context被静态持有

calss Util{
    private static Context context;
    public static void utils(Context context){
        this.context = context;
    }
}

因为context被静态持有后, 生命周期太长, 当context的Activity已经退出时, 应该被回收, 但依然被Util持有, 导致Activity对象的内存无法回收,

解决办法:
1,用Application的context, 类似于Dialog这种必须要Activity的Context的, 可以使用其他方法,
2,不要将context传给静态变量,也不要传给其他生命周期可能会长于Activity的对象

3,Handler 这篇文章解释的很好

其实用了RxJava之后, 现在Handler用的比较少了, 我们用Handler就是因为线程切换, 既然有其他线程, 那其他线程的生命周期就可能比Activity长, 当Activity结束后, Handler还在执行, 就会内存泄漏

calss Activity{
    Handler mHandler = new Handler(){
        handlerMessage(Message msg){//响应}
    }
    onCreate(){
        mHandler.sendEmptyMessageDelayed(0, 1000 * 2);//延时
    }
}

解决办法:
1, 引入软引用
  强引用的对象, 就绝不收回,
  软引用的对象,是能不收回就不收回,这里的能不收回就是指内存足够的情况,
  弱引用的对象,是发现就收回,但是一般情况下不会发现

private static class NoLeakHandler extends Handler{
        private WeakReference<NoLeakActivity> mActivity;

        public NoLeakHandler(NoLeakActivity activity){
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

每次使用前注意mActivity的判空

2, 及时解除关系,这样也能防止泄露, 因为handler已经不在持有Activity

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}

4, 静态集合存储对象

Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
    Object o = new Object();
    v.add(o);
    o = null;
}

Vector的生命周期跟随整个应用, 虽然变量名o不再持有对象, 但v一直持有, 导致new出来的对象无法回收, 导致内存泄漏

解决办法 将v置为null

总结

  Bitmap 没调用 recycle()方法,对于 Bitmap 对象在不使用时,我们应该先调用 recycle() 释放内存,然后才它设置为 null. 因为加载 Bitmap 对象的内存空间,一部分是 java 的,一部分 C 的(因为 Bitmap 分配的底层是通过 JNI 调用的 )。 而这个 recyle() 就是针对 C 部分的内存释放
  对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露
  对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏

将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量

另外一篇讲原理
这个作者我很喜欢

一个不错的评论
1.JVM 的内存模型和 Android 虚拟机是有一点区别,JVM 基于栈,但 Android 虚拟机基于寄存器,所以 Android 虚拟机栈中不存在局部变量表这种说法,里头其实是一组虚拟寄存器。
2.博主拿对象的引用在栈上的情况来解释内存泄露是不恰当的,因为栈上引用的对象是不会导致内存泄露的,当一个方法执行完的时候,对象引用自然就置空了。 只有在成员变量上引用的对象会导致内存泄露,因为引用是分配在堆上的。