内存泄漏

内存泄漏:常见原因及措施

原因

  1. 资源对象没关闭造成的内存泄漏
  2. 变量的作用域不一样导致
  3. 内存压力过大

常见解决方法思路

  1. 尽量使用Application的Context而不是Activity的

    因为很多对象的生命周期不是和Activity同步的,而是与Application一致

  2. 使用弱引用和软引用

    更多的使用是在内部类里面

  3. 手动设置null,解除引用关系
  4. 将内部类设置为static, 不隐式的持有外部的对象
  5. 注册与反注册成对出现,在对象适合的生命周期进行反注册操作
  6. 如果没有修改的权限,比如系统或第三方的SDK,可以使用反射来操作持有的关系

Handler 引起的内存泄漏

在一个类中(Activity), handler在进行异步的操作并处理返回结果经常被使用,

  • android 应用启动时:

    启动时,会自动创建一个供应用主线程使用的Looper实例,Looper的主要工作是一个一个的处理消息队列中的消息对象。
    在android中所有Android框架事件(比如activity的生命周期调用和按钮点击等)都是放在
    消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。
    主线程中的Looper生命周期和当前应用一样长。

  • Handler在主线程进行初始化后

    会发送一个target为这个Handler的消息到Looper处理的消息队列中,实际上已经发送的消息
    包含一个Handler实例的引用,只有这样Looper在处理这条消息时才可以调用
    Handler.handleMessage(message);

  • 在java中:

    非静态的内部类和匿名内部类都会隐式的持有其外部类的引用。静态的内部类不会持有外部类的引引用。

在activity中,如果activity被finish掉,但是Handler未执行完毕,就会持有一个activity的对象,导致activity无法回收,而activity持有的资源也无法得到回收,,造成内存泄漏。

解决这个问题,避免使用非静态内部类,继承Handler时,要么放在单独的类文件下,要么就是使用静态内部类。同样也可以使用弱引用。

还有一个问题
当activity被finish后,handler对象没有必要继续处理消息了,所以需要对消息处理:
在Activity onStop() 或者onDestroy()的时候,取消掉该Handler对象的MessageRunnable.
通过查看Handler的API,它有几个方法,: removeCallBack(Runnable r)removeMessage(int what)

@Override
public void onDestroy() {
    mHandler.removeMessages(MESSAGE_1);
    mHandler.removeMessages(MESSAGE_2);
    mHandler.removeMessage(MESSAGE_3);
    ...
    mHandler.removeCallbacks(mRunnable);
    ...
    //如果觉着太麻烦,可这样操作
    //mHandler.removeCallbackAndMessage(null);
}

Thread 引起的内存泄漏

很大部分的原因和Handler是一样的,
解决办法:

  1. 将线程的内部类,改为静态内部类
  2. 在线程内部采用弱引用保存Context引用。

    public class MainActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyThread(this).start();
    }
    private void doSomethings() {
        //
    }
    ...
    private static class MyThread extends Thread {
        WeakReference<MainActivity> mThread;
        public MyThread(MainActivity activity) {
            mThread = new WeakReference<MainActivity>(activity);
        }
        @Override
        public void run() {
            super.run();
            if (mThread == null) {
                return;
            }
            if (mThread.get() != null) {
                mThread.get().doSomething();
            }
        }
    }
    

    }

上面其实是切换两个对象的双向强引用链接

  1. 静态内部类:切断Activity对于MyThread的强引用
  2. 弱引用:切断MyThread对于Activity的强引用

AsyncTask内部类会如何
事实上AsyncTask的问题更加严重,Thread只有在run函数不结束才会出现内存泄漏,而AsyncTask内部的实现机制是运用了ThreadPollExcutor
该类产生的Thread对象的生命周期是不确定的,是程序无法控制的,所有,更容易出现内存泄漏的问题。

如果让使用非静态内部类的时候, 要格外注意,如果其实例的持有对象的生命周期大于外部类对象,那么就可能导致内存泄漏。

Android App 资源内存泄漏

资源内存泄漏主要是资源申请未释放,资源没有重复使用

  1. 申请资源后能保证及时释放资源
  2. 利用复用机制,如池的概念

1. 引用资源没有释放

private final class SettingObserver implements Observer {
    public void update(Observable 0, Object arg) {
        //todo
    }
}
...
ContentQueryMap.getInstance().addObserver(new SettingObserver());

这段代码存在问题,存在的问题是没有办法注销观察者(SettingObserver),
这个对象将伴随整个单例生命周期,无形中就泄漏一个SettingsObserver的内存。

1.1 注册未取消造成的内存泄漏
由于四大组件的生命周期不一致引起的,
例如:BroadcastReceiver对象的注册,Observer对象,
被观察对象生命周期和观察者的生命周期不一致,不观察的时候需要注销。

1.2 集合中对象没清理造成的内存泄露
把一些对象的引用加入到集合中,当不需要的时候,如果没有把它的引用清除掉,
会使这个集合越来越大。如果是static的话,那情况更严重了。
add()后,要记得remove().

2. 资源对象没关闭造成的内存泄漏
资源性对象,比如(Cusor File文件等)往往都用了一些缓冲,不使用的时候,需要及时关闭。
它们的缓冲不仅存在与java虚拟机内,还存在与java虚拟机外.
如果把它们的引用设置为Null,而不关闭它们,往往会造成内存泄漏。
因为有些资源性对象,比如SQLiteCursor,如果没关闭,系统回收时会关闭,但效率太低。 因此对于资源性对象在不使用的时候,应该调用它的close()`函数。将其关闭掉,然后才置为null.

//不合理的写法
try {
    Cursor c = queryCursor();
    int a = c.getInt(1);
    ...
    //如果执行出错, 后面的cursor.close()将不会执行
    ...
    c.close();
} catch (Exception e) {
    ...
}

这样的写法存在问题,合理的写法为:

Cursor c;
try {
    c = queryCursor();
    int a = c.getInt();
    ...
} catch (Exception e) {
} finally {
    if (c != null) {
        c.close();
    }
}

3. 一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是对没使用的内存没进行有效及时的释放,
或是没有有效的利用已有的对象而是频繁的申请新内存,造成了不必要的内存开支。

常见优化方式

  1. 频繁的申请内存对象和释放对象,可以考虑pool池
  2. 多线程可以考虑线程池
  3. 频繁的申请对象释放对象。可以考虑对象池,
  4. 频繁的链接资源和释放资源,可以考虑链接资源池

Bitmap 没调用recycle()
Bitmap对象不使用时,先调用recycle()释放内存,然后才设置为null.

chenzhao@hustunique.com