1. 概述
Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。
2. 常见泄露类型
2.1. 集合类泄露
如果集合类仅仅有添加元素,而没有相应的删除机制,会导致内存被占用。当将集合中元素置空,但是集合因为持有对元素的引用,导致内存回收不,而发生内存泄露。解决方法是,可先删除元素然后置空,或者直接将集合置空。
Android 中常见的集合类内存泄露有ValueAnimator调用addUpdateListener,EditText调用addTextChangedListener()而未注销监听导致内存泄露,他们代码如下
//ValueAnimator
public void addUpdateListener(AnimatorUpdateListener listener) {
if (mUpdateListeners == null) {
mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
}
mUpdateListeners.add(listener);
}
//EditText
public void addTextChangedListener(TextWatcher watcher) {
if (mListeners == null) {
mListeners = new ArrayList<TextWatcher>();
}
mListeners.add(watcher);
}
2.2 静态变量引起的内存泄漏
在java中静态变量的生命周期是在类加载时开始,类卸载时结束。换句话说,在android中其生命周期是在进程启动时开始,进程死亡时结束。所以在程序的运行期间,如果进程没有被杀死,静态变量就会一直存在,不会被回收掉。如果静态变量强引用了某个Activity中变量,那么这个Activity就同样也不会被释放,即便是该Activity执行了onDestroy(不要将执行onDestroy和被回收划等号)。这类问题的解决方案为:
- 寻找与该静态变量生命周期差不多的替代对象。
- 若找不到,将强引用方式改成弱引用
2.2.1 单例引起的Context内存泄漏
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。如果单例中引用activity类型Context而非ApplicationContext,会导致ondestroy后不能被回收
public class IMManager {
private Context context;
private static IMManager mInstance;
public static IMManager getInstance(Context context) {
if (mInstance == null) {
synchronized (IMManager.class) {
if (mInstance == null)
mInstance = new IMManager(context);
}
}
return mInstance;
}
private IMManager(Context context) {
this.context = context;
}
}
可以让传入的context 转化为ApplicationContext。
2.2.2 静态Activity和View,drawable
静态变量Activity和View会导致内存泄漏,在下面这段代码中对Activity的Context和TextView设置为静态对象,从而产生内存泄漏。
public class MainActivity extends AppCompatActivity {
private static Context context;
private static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
textView = new TextView(this);
}
}
静态变量drawable 也类似,但android 4.0的Drawable.Java对setCallback的实现进行了软引用,避免了内存泄露:
public final void setCallback(Callback cb){
mCallback = newWeakReference<Callback> (cb);
}
2.2.3 非静态内部类创建静态实例
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}
因为非静态内部类默认会持有外部类的引用,而该非静态内部类TestResource又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
2.3. 线程造成内存泄露
android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类/非静态内部类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露
2.3.1 多线程
如果多线程中含有外部activity的引用,当activity销毁后,而多线程任务还未执行完,因为线程持有对activity的引用而导致activity不能被回收掉
2.3.2 内部线程造成内存泄露
public class LeakActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
leakFun();
}
private void leakFun(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
可以将leakFun()加上static 改成静态方法,让匿名内部类不会持有LeakActivity.this应用。
2.3.1 handler
public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
fetchData();
}
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
// 刷新数据
break;
default:
break;
}
};
};
private void fetchData() {
//获取数据
mHandler.sendEmptyMessage(0);
}
}
mHandler 为匿名内部类实例,会引用外围对象LeakAty.this,如果该Handler在Activity退出时依然还有消息需要处理,那么这个Activity就不会被回收。
如果当Handler为非静态内部类也会导致内存泄露,因为非静态内部类也会持有外部类的引用,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。
2.3.2 AsyncTask
AsyncTask 情况类似handler,都有由于非静态内部类,或者匿名内部类持有activity的引用,当activity退出时任务还未完成继续持有对activity的引用,导致activity不能回收。
2.4. 动画导致的内存泄露
Android在动画使用过程如果不注意也会经常导致内存泄露。
例如属性动画onDestroy中未停止动画,这时候Activity会被View所持有,从而导致Activity无法被释放。解决方法onDestroy去去调用objectAnimator.cancel()来停止动画。
public class LeakActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
textView = (TextView)findViewById(R.id.text_view);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
}
}
2.5 资源未关闭或注销引用内存泄露
常见由于资源未关闭/注销导致内存泄露有
- BraodcastReceiver
- ContentObserver
- File
- 游标 Cursor
- Stream
- Bitmap
...
这些资源应该打开,用完后马上关闭,或者在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
2.6. 不良代码
- 构造 Adapter 时,没有使用缓存的 convertView ,每次都在创建新的 converView
- EventBus,RxJava等一些第三开源框架的使用,若是在Activity销毁之前没有进行解除订阅将会导致内存泄漏。
···
3. 内存泄露解决措施
3.1. 工具方面:
- LeakCanary 检测 Android 的内存泄漏
- Android Studio 的Monitor监测内存使用情况
- MAT分析heap的总内存占用大小来初步判断是否存在泄露
3.2 代码方面
- 引入弱引用
- 使用合适的Context,尽量使用ApplicationContext
- 在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。
- 尽量避免使用 static成员变量
- 使用的资源及时关闭、注销
- 避免非静态内部类,可改成静态内部类
- 避免匿名内部类