内存泄漏是指程序已经不会再使用的内存对象,由于 GC 时无法识别,不能及时地回收,一直保留在内存中占用存储空间,不释放给其他对象。
当越来越多的内存泄漏发生时(也可能是频繁地运行了导致内存泄漏这块的程序),系统为应用分配可用堆上的空间就会不断变小,会导致不断启动垃圾回收去释放空间用于执行其他程序(在 Logcat 上可以看到系统不停地打印出 GC 日志)就会造成很多性能问题。
对内存泄漏的优化是 Android 内存优化中很重要的一点。
资源性对象(比如Cursor、File文件等)往往都用了一些缓冲,在不使用时,应该及时关闭它们,以便它们的缓存数据能够及时回收。它们的缓存不只存在于Java虚拟机内,还存在于Java虚拟机外。如果仅仅是把它的引用设置为 null,而不关闭它们,往往会造成内存泄漏。
在编写资源文件读写时,需要在 finally 中关闭资源性对象,避免在异常情况下资源对象未被释放的隐患。
如果事件注册后未注销,会导致观察者列表中维持着对象的引用,阻止垃圾回收,一般发生在注册广播接收器、注册观察者等。
静态变量长期维持对象的引用,阻止垃圾回收,如果静态变量持有大的数据对象,如Bitmap等,就很容易引起内存不足等问题。
非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被系统回收。
代码示例:
public class MyActivity extends Activity {
private static TestModule mTestModule = null;
public void onCreate(...) {
mTestModule = new TestModule();
}
class TestModule {...}
}
示例分析:
在 Activity 内部创建了一个非静态内部类的静态实例 mTestModule,每次启动 Activity 时都会使用 mTestModule 的数据,这样虽然避免了资源的重复创建,但是 TestModule 为非静态内部类默认持有外部类的引用,而这里又使用该非静态内部类创建了一个静态的实例,该实例的生命周期和应用一样长,这就导致该静态实例一直持有该 Activity 的引用,Activity 的内存资源不能正常回收。
解决方案:
Handler 通过发送 Message 与主线程交互,Message 发出之后存储在 MessageQueue 中,有些 Message 也不是马上就被处理到。在 Message 中存在一个 target,它是 Handler 的一个引用,Message 在 Queue 中存在的时间过长,就会导致 Handler 无法被回收。
代码示例:
public class MyActivity extends Activity {
Handler mHander = new Handler() {
public void handleMessage() {
}
}
}
示例分析:
由于 mHandler 是 Handler 的非静态匿名内部类的实例,所以它持有外部类 Activity 的引用,并且消息队列是在一个 Looper 线程中不断轮询处理消息,那就有一种情况,当这个 Activity 退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的 Message 持有 mHandler 实例的引用,mHandler 又持有 Activity 的引用,所以导致该 Activity 的内存资源无法及时回收,引发内存泄漏。
解决方案:
public class MyActivity extends Activity {
NewHandler mHander = new NewHandler(this);
public void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
}
private static class NewHandler extends Handler {
private WeakReference mContext = null;
public NewHandler(Context ctx) {
mContext = new WeakReference(ctx);
}
public void handleMessage() {
}
}
}
把一些对象的引用加入集合中,在不需要该对象时,如果没有把它的引用从集合中清理掉,这个集合就会越来越大。如果这个集合是 static,情况就更严重。
Android 中的 WebView 不仅仅存在很大的兼容性问题,不同 Android 系统版本中的 WebView 会有较大的差异,加上不同厂商定制的 ROM 中的 WebView 也存在着差异。更严重的是 WebView 都存在内存泄漏的问题,在应用中只要使用一次 Webview,内存就不会被释放掉。通常解决这个问题的办法是为 WebView 开启独立的一个进程,使用 AIDL 与应用的主进程进行通信,WebView 所在的进程可以根据业务的需要选择合适的时机进行销毁,达到正常释放内存的目的。
LeakCanary 是 Square 公司开源的功能非常强大的内存泄露检测工具,使用它来检测内存泄露简洁而方便。
网上有很多它的使用教程,在这儿只作推荐,就不详细说了。
如果在内存泄漏发生后再去找原因并修复会增加开发的成本,最好是在编写代码时就能够很好地考虑到内存问题,写出更高质量的代码,在此列举了一些常见的内存泄漏场景,在以后的开发过程中需要避免这类问题。