什么是内存泄露?
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工具
- 打开DDMS视图
- 选中Devices下某个具体的应用程序
- 选中Devices下第二个小绿点Update Heap
- 不断运行程序并点击Cause GC
- 关注data Object行、Toal Size列
- 耍你的APP去吧,如果发现Toal Size越来越大,很可能有内存泄露的发生、
MAT(Memory Analyzer Tool)工具
导出.hprof文件
- 打开DDMS视图
- 选中Devices下某个具体的应用程序
- 选中Devices下第二个小绿点Update Heap
- 点击Cause GC
- 点击Dump HPROF file
- 切换到MAT页卡,默认如下图所示