一、内存泄露的定义:
内存泄露是指不再使用的内存仍然占用着内存空间,因为程序中仍然保存着对它的引用,而使得GC无法将它回收或得到及时释放,从而造成的内存空间浪费的问题,称为内存泄露。
二、内存泄露的根本原因:
长生命周期的对象持有短生命周期对应的引用,因为短生命周期对象可能不再使用,而因为长生命周期对象持有着对其的引用,因此GC无法将其进行回收。
三、内存泄露(Memory Leak)与内存溢出(OOM)之间的区别:
内存溢出(OOM)是指程序在申请内存时,没有足够的内存空间供其使用,出现OOM;
Android Dalvik虚拟机的内存大致可以分为三类:Java Heap Object,Bitmap Memory, Native Object;Dalvik虚拟机在启动的时候,就是通过读取系统属性dalvik.vm.heapsize的值来获得Java Object Heap的最大值(一般 为16M),当应用使用的内存超过16M时,便会发生OOM;而Bitmap Memory是专门用来处理图像的,Bitmap直接在Java native Object中进行分配的,故平时在进行图片开发时,才会对图片的处理如此谨慎,避免发生OOM;
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,即发生OOM;
四、造成内存泄露的情况包括有:
1)静态集合类造成的内存泄露;静态变量存储在方法区中,其生命周期与程序相同,而其内部又保存着对对象的引用,当这些对象其实不会再使用时,也依然无法释放;
因此当该静态集合类不再使用,或者静态集合类中保存的对象确定不会再使用时,要注意设为null;
static Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
Object o = new Object();
v.add(o);
o = null; // 设置null也无法将其内存回收,因为Vector中仍保存着对该内存的引用
}
2)在观察者模式中,添加对一个对象的监听器,当对象不再使用时,而未对监听器进行释放。
这个在Android中情况尤为严重,比如我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。
对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process进程挂掉。虽然有些系统程序,它本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在我们的程序中明确的取消注册,程序结束时应该把所有的注册都取消掉。
还有如调用了registerReceiver方法,而没有调用unregisterReceiver方法,造成的内存泄露等;
3)单例模式引起的内存泄露:
class A {}
class Test {
private static Test instance = new Test(new A());
private A a;
private Test(A a) {
this.a = a;
}
public Test getInstance() {
return instance;
}
}
设计上出现的问题,因为静态变量存储在方法区,因此instance的生命周期与应用相同,而instance中又保存了对A对象的引用,使得即使A对象不再使用,也无法对其进行释放。
4)变量生命周期使用不当:
class Server{
private String msg;
public void recvMsg(){
readMsg(msg);
save(msg);
// 这里msg被保存之后已经无用,但是其生命周期将与Server保持一致,不能被回收,如果msg太大,将会造成不可预测的意外,安全的做法是加一行代码:msg=null;
}
}
1、变量的作用域需要合理设置,类的成员变量生命周期与类对象相同,对于一些只需要短生命周期的变量,则会造成内存空间的浪费;
而这个也需要与内存抖动之间进行平衡比较,避免短时间内创建大量的临时性变量而造成内存抖动。
2、同理对于static静态变量,也应当遵循谨慎的原则进行使用;
3、在Android中最突出的问题便是对Context的引用造成的内存泄露。因为Activity对象本身引用的资源较多,其无法进行释放造成的资源浪费效果将会很明显;因此应当注意避免让长生命周期的对象引用Activity,可以尝试引用Application Context;
5)内部类与外部类之间的引用造成的内存泄露;由于内部类保存着对外部类的引用,若未对其进行释放,外部类的内存空间也无法释放;
1、最典型的便是Handler造成的内存泄露:
由于MyHandler是内部类,初始化MyHandler,其内部会隐式地保存对其外部类即Activity的引用;
使用MyHandler postDelayed一个Runnable,该操作会send一个Message到MessageQueue中,而Message对象msg中的target变量将会指向myHandler,即msg保存有对myHandler的引用
又MessageQueue是针对线程存在,当msg入队列还未得到处理时,Activity被关闭,此时msg依然存在与MessageQueue中;
又由前面得知msg保存着对myHandler的引用,而myHandler保存着对Activity的应用;因此Activity关闭后,其内存仍然无法得到回收,即造成内存泄露。
2、解决Handler内存泄露的方法:使用弱引用及静态内部类的方法;
6)涉及到ListView优化中的未使用convertView,过多使用自己创建的View;
1、错误用法:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = inflater.inflate(R.layout.activity_main, parent, false);
// ......
return view;
}
2、由ListView的源码及工作原理,来看一下ListView的源码:
ListView填充及获取View的方法如下:
由RecycleBin机制,ListView会首先尝试获取ActiveView[]中存储的View;然后再通过obtainView来获取View;
obtainView方法中首先通过getScrapView获取ScrapView[]中存储的View,ScrapView中存储的View便是划出界面废弃的View存储起来,便于再次重复利用;
可以看到在child = mAdapter.getView(position, scrapView, this);方法中来获得View,而scrapView即对应重写BaseAdapter方法中的convertView变量;不使用convertView则会造成废弃的View无法重复利用,同时大量创建的View,当View移出界面之后,而无法得到及时回收,造成内存泄露。
3、正确的用法:
7)资源没有关闭造成的内存泄露:
1、Cursor、File等资源型对象:
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。
它们的 缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。
如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。
2、WebView对象:要及时调用destory()方法销毁该对象;
3、Bitmap的回收:Bitmap占用的内存空间较大,在不对其使用时,要注意调用recycle()方法对其进行回收。具体参考图片优化
4、InputSteam,OutputStream等输入输出缓冲流在使用完成后要注意进行关闭。
5、动画未关闭:属性动画中有一类无限循环的动画,若Activity销毁时并未关闭动画,则动画会一直运行,由于属性动画持有View对象,View对象持有其相关的Activity对象,会造成Activity无法释放而内存泄露
五、处理内存泄露的方法:
除了针对上述内存泄露的原因进行相应的处理外,还可以通过mat工具对内存泄露进行分析。
http://www.jianshu.com/p/c49f778e7acf
http://blog.csdn.net/yulianlin/article/details/50393872