1. 内存泄露简介
内存泄露,即Memory Leak,指程序中不再使用到的对象因某种原因从而无法被GC正常回收。发生内存泄露,会导致一些不再使用到的对象没有及时释放,这些对象占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,内存空间不足而出现OOM(内存溢出)。无用对象占据的内存空间越多,那么可用的空闲空间也就越少,GC就会更容易被触发,GC进行时会停止其他线程的工作,因此有可能会造成界面卡顿等情况。
为什么不再使用到的对象无法被GC正常回收呢?这是因为还有其他对象持有无用对象的引用。为什么其他对象会持有无用对象的引用呢?这通常是我们意外地引进了无用对象的引用。从而导致无用对象无法给正常回收。
常见的内存泄露点
-
静态变量
-
非静态内部类(匿名类)
-
集合类
-
使用资源对象后未关闭
后面会对这些内存泄露点逐一分析。
2. 常见内存泄露例子及解决方案
2.1 静态变量内存泄露
说明:静态变量的生命周期跟整个程序的生命周期一致。只要静态变量没有被销毁也没有置null,其对象就一直被保持引用,也就不会被垃圾回收,从而出现内存泄露。
下面的例子即是静态变量引起的内存泄漏
public class MainActivity extends AppCompatActivity {
private static Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
}
咱们用lint(android studio自带的内存泄漏工具)对代码进行分析
步骤:
2.2 非静态内部类(匿名类)内存泄露
说明:非静态内部类 (匿名类)默认就持有外部类的引用,当非静态内部类(匿名类)对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。
2.2.1 Handler内存泄露
这里会涉及到Handler
的原理,如果还不懂Handler
原理的话,建议先去看下。
如果Handler
中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler
继续执行而导致Activity
发生泄漏。
1.首先,非静态的
Handler
类会默认持有外部类的引用,包含Activity
等。
2.然后,还未处理完的消息(Message
)中会持有Handler
的引用。
3.还未处理完的消息会处于消息队列中,即消息队列MessageQueue
会持有Message
的引用。
4.消息队列MessageQueue
位于Looper
中,Looper
的生命周期跟应用一致。
因此,此时的引用关系链是Looper
-> MessageQueue
-> Message
-> Handler
-> Activity
。所以,这时退出Activity
的话,由于存在上述的引用关系,垃圾回收器将无法回收Activity
,从而造成内存泄漏。
内存泄漏:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyHandler().sendEmptyMessageDelayed(0,20000);
}
public class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0){
Log.d("MainActivity","delaymessage");
}
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
Log.d("activity","onBackPressed");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d("MainActivity","onDestroy");
}
解决方法:
-
静态内部类+弱引用
静态内部类默认不持有外部类的引用,所以改成静态内部类即可。同时,这里采用弱引用来持有Activity
的引用。
private static class MyHalder extends Handler {
private WeakReference<Activity> mWeakReference;
public MyHalder(Activity activity) {
mWeakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//...
}
}
-
Activity
退出时,移除所有信息
移除信息后,Handler
将会跟Activity
生命周期同步。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
2.2.2 多线程引起的内存泄露
我们一般使用匿名类等来启动一个线程,如下:
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
同样,匿名Thread
类里持有了外部类的引用。当Activity
退出时,Thread
有可能还在后台执行,这时就会发生了内存泄露。
解决方法:
-
静态内部类
静态内部类不持有外部类的引用,如下:
private static class MyThread extends Thread{
//...
}
-
Activity
退出时,结束线程
同样,这里也是让线程的生命周期跟Activity
一致。
其他非静态内部类(匿名类),都可以按照这个套路来:一个是改成静态内部类,另外一个就是内部类的生命周期不要超过外部类。
2.3 未关闭资源对象内存泄露
说明:一些资源对象需要在不再使用的时候主动去关闭或者注销掉,否则的话,他们不会被垃圾回收,从而造成内存泄露。
以下是一些常见的需要主动关闭的资源对象:
-
1.注销广播
如果广播在Activity
销毁后不取消注册,那么这个广播会一直存在系统中,由于广播持有了Activity
的引用,因此会导致内存泄露。
unregisterReceiver(receiver);
-
2.关闭输入输出流等
在使用IO、File流等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果不及时关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就应该及时关闭,以便缓冲能得到释放,从而避免内存泄露。
InputStream.close();
OutputStream.close();
-
3.回收Bitmap
Bitmap
对象比较占内存,当它不再被使用的时候,最好调用Bitmap.recycle()
方法主动进行回收。
Bitmap.recycle();
Bitmap = null;
-
4.停止动画
属性动画中有一类无限动画,如果Activity
退出时不停止动画的话,动画会一直执行下去。因为动画会持有View
的引用,View
又持有Activity
,最终Activity
就不能给回收掉。只要我们在Activity
退出把动画停掉即可。
animation.cancel();
-
5.销毁WebView
WebView
在加载网页后会长期占用内存而不能被释放,因此我们在Activity
销毁后要调用它的destory()
方法来销毁它以释放内存。此外,WebView
在Android 5.1上也会出现其他的内存泄露。具体可以看下这篇文章:WebView内存泄漏解决方法。
所以,要防止WebView
内存泄露还是比较复杂的。代码如下:
@Override
protected void onDestroy() {
if( mWebView!=null) {
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
mWebView.destroy();
}
super.on Destroy();
}