众所周知,在Java中有些对象的生命周期是有限的,当它们完成了特定的逻辑后将会被垃圾回收;但是,如果在对象的生命周期本来该被垃圾回收时这个对象还被别的对象所持有引用,那就会导致内存泄漏;这样的后果就是随着我们的应用被长时间使用,他所占用的内存越来越大。如下就是一个最常见简单的泄露例子(其它的泄露不再一一列举了):
public final class MainActivity extends Activity {
private DbManager mDbManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//DbManager是一个单例模式类,这样就持有了MainActivity引用,导致泄露
mDbManager = DbManager.getInstance(this);
}
}
可以看见,上面例子中我们让一个单例模式的对象持有了当前Activity的强引用,那在当前Acvitivy执行完onDestroy()后,这个Activity就无法得到垃圾回收,也就造成了内存泄露。
*内存泄露可以引发很多的问题,常见的内存泄露导致问题如下:
-
应用卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC);
-
应用被从后台进程干为空进程(上面系统内存原理有介绍,也就是超过了阈值);
-
应用莫名的崩溃(上面应用内存原理有介绍,也就是超过了阈值OOM);
造成内存泄露泄露的最核心原理就是一个对象持有了超过自己生命周期以外的对象强引用导致该对象无法被正常垃圾回收;可以发现,应用内存泄露是个相当棘手重要的问题,我们必须重视。
*Context使用不当造成内存泄露;不要对一个Activity Context保持长生命周期的引用(譬如上面概念部分给出的示例)。尽量在一切可以使用应用ApplicationContext代替Context的地方进行替换(原理我前面有一篇关于Context的文章有解释)。
-
非静态内部类的静态实例容易造成内存泄漏;即一个类中如果你不能够控制它其中内部类的生命周期(譬如Activity中的一些特殊Handler等),则尽量使用静态类和弱引用来处理(譬如ViewRoot的实现)。
-
警惕线程未终止造成的内存泄露;譬如在Activity中关联了一个生命周期超过Activity的Thread,在退出Activity时切记结束线程。一个典型的例子就是HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了Activity生命周期,我们必须手动在Activity的销毁方法中中调运thread.getLooper().quit();才不会泄露。
-
对象的注册与反注册没有成对出现造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。
-
创建与关闭没有成对出现造成的泄露;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。
-
不要在执行频率很高的方法或者循环中创建对象,可以使用HashTable等创建一组对象容器从容器中取那些对象,而不用每次new与释放。
-
避免代码设计模式的错误造成内存泄露。