内存泄漏如何避免
1. 内存泄漏
是指程序中动态分配的堆内存由于某种原因没有及时回收,造成系统内存的浪费,导致程序运行变慢甚至是崩溃的结果。
根本原因:
一个生命周期较长的对象,强引用持有了一个生命周期较短的对象,导致短生命周期的对象在应该被销毁的时候没有被销毁。
2. 具体场景和避免办法
2.1 单例模式
项目中经常会用到一堆的工具类,有些工具类基本上就是全局单例的模式,而且它们在使用的时候又会经常的需要用到Context这个上下文变量,当传入这个变量的时候,这些工具类就会持有对应Activity的强引用了。
public class SingleInstance {
//像这样的单例工具类,调用的时候都是直接使用instance变量
//要更新的时候就会调用newInstance
private static SingleInstance instance;
private Context mContext;
private SingleInstance(Context context){
mContext = context;
}
private static SingleInstance getInstance(Context context){
if (instance == null)
return new SingleInstance(context);
return instance;
}
}
public class MainActivity extends AppCompatActivity {
private static MainActivity instance;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = this;
}
}
这种情况下Activity退出后,由于这些单例还持有Activity的强引用,所以导致分配给Activity的堆内存就没法回收,这就造成了内存泄漏。
避免办法:
- 一些生命周期比较短的对象,不要作为成员变量传入,也不要被任何长期对象持有。
- 用弱引用代替强引用,这样不会阻拦GC回收对象。
public class SingleInstance {
//像这样的单例工具类,调用的时候都是直接使用instance变量
//要更新的时候就会调用newInstance
private static SingleInstance instance;
private Context mContext;
private SingleInstance(){}
private fun(Context context) {
// 处理逻辑
}
private static SingleInstance getInstance(){
if (instance == null)
return new SingleInstance();
return instance;
}
}
```java
public class MainActivity extends AppCompatActivity {
private static WeakReference<MainActivity> instance;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = new WeakReference<>(this);
}
}
2.2 非静态内部类
在Java中,非静态内部类会隐形持有外部类的引用,而且是强引用。
2.2.1 内部类/匿名内部类
有时候经常会需要临时new一个Thread出来工作的情况,这种时候这个临时工线程就会拥有Activity的引用了,这种情况下如果Activity退出销毁后,也不会回收内存,这就又造成内存泄漏了。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
try {
//假装在做耗时的工作
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
2.2.2 Handler
还有Handler的内部类使用,我们经常也是这么用的,这个就更夸张了,Android Studio就直接报警告提醒你会内存泄漏了。
Handler的生存周期并不会因为你是写在Activity里面就和Activity一样,所以会造成内存泄漏。
2.2.3 AsyncTask
这个也是会不经意间就直接写成Activity内部类的类型,也会持有Activity的强引用。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyasyncTask().execute();
}
static class MyAscnyTask extends AsyncTask<Void, Void, Void>{
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
避免办法:
- 还是和之前一样,改用弱引用来代替强引用。
- 使用静态变量,这样就无法持有外部类的引用了。
2.3 集合/静态集合
在开发过程中经常会有用到集合的时候,集合内的元素会随着添加而越来越多,如果是静态集合那么生命周期会和应用程序一样长,这样就非常占内存。
static List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object obj = new Object();
objectList.add(obj);
obj = null;
}
避免办法:用不到的集合元素要及时的清理。
2.4 静态变量
静态变量本身的生命周期就和应用程序一样长,不当的使用自然会造成内存泄漏。
避免办法:正确的使用静态变量…之前说的集合那些就避免用静态变量就行了,不得不用的时候就记得清理。
2.5 资源未释放
在开发过程中,经常会有应该及时关闭/释放的对象却没有记得这么做,这里就列举一下有哪些
- Service:bindService()忘记unbindService()。
- BroadcastReceiver:registerReceiver()后忘记unregisterReceiver()。
- 文件流,SQLite等需要关闭的对象:open()之后忘记close()。
- 加载的图片资源Bitmap:这个是图片,占内存比较多,不用的时候一定要记得关闭,调用recycle()方法。
参考材料
Android 关于内存泄露,你必须了解的东西 - 简书
https://www.jianshu.com/p/65f914e6a2f8
一篇技术好文之Android性能优化内存泄漏无处可藏(图文) - 简书
https://www.jianshu.com/p/86a6d5cd3b05