原创不易,尊重作者,转载请注明出处
前言
内存泄露是指当一个对象已经不需要再使用本该回收时候,另外一个正在使用的对象持有它的引用从而导致它不能被回收,该对象就会停留在堆内存中,这就产生了内存泄露。
内存泄露的影响
内存泄露是造成App出现OOM的主要原因。
检测内存泄露的方法
借助两个工具:MAT、LeakCanary
MAT:强大的内存分析工具。
LeakCanary:Square开源的一款轻量级的第三方内存泄漏检测工。
常见的内存泄露及解决办法
1、单例造成的内存泄露
由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,那么就会使得该对象不能被正常回收,从而导致内存泄露。
典型例子:
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context;
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
}
return sInstance;
}
}
由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1).如果此时传入的是 Application 的 Context,因为 Application
的生命周期就是整个应用的生命周期,所以这将没有任何问题。
2).如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该
Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity
退出时它的内存并不会被回收,这就造成了内存泄漏。
解决办法:
⑴获取Application的Context
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context.getApplicationContext();
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
}
return sInstance;
}
}
⑵使用弱引用的方式
public class Sample {
private WeakReference<Context> mWeakReference;
public Sample(Context context){
this.mWeakReference = new WeakReference<>(context);
}
public Context getContext() {
if(mWeakReference.get() != null){
return mWeakReference.get();
}
return null;
}
}
被弱引用关联的对象只能存活到下一次垃圾回收之前,也就是说即使 Sample 持有 Activity 的引用,但由于 GC 会帮我们回收相关的引用,被销毁的 Activity 也会被回收内存,这样我们就不用担心会发生内存泄露了。
2、非静态内部类创建静态实例造成的内存泄漏
对比 | 静态内部类 | 非静态内部类 |
---|---|---|
与外部 class 引用关系 | 如果没有传入参数,就没有引用关系 | 自动获得强引用 |
被调用时需要外部实例 | 不需要 | 需要 |
能否调用外部 class 中的变量和方法 | 不能 | 能 |
生命周期 | 自主的生命周期 | 依赖于外部类,甚至比外部类更长 |
从上表可以看出,非静态内部类自动获取外部类的强引用,而它的生命甚至比外部类更长,如果一个activity的非静态内部类的生命周期比activity更长,那么activity的内存便无法被回收,还有可能发生空指针问题。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
我们在activity中继承AsyncTask自定义了一个非静态内部类,在doInbackground方法中做了耗时操作,然后在onCreate中启动MyAsyncTask,如果在耗时操作结束前,activity被销毁了,这时候因为MyAsyncTask持有activity的强引用,便会导致activity的内存无法被回收。
解决办法:
将非静态内部类变成静态内部类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
3、匿名类造成的内存泄露
匿名类和非静态内部类有相同的共同点是持有外部类的引用,匿名类造成内存泄露的原因也跟非静态内部类基本一样。
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ① 匿名线程持有 Activity 的引用,进行耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// ② 使用匿名 Handler 发送耗时消息
Message message = Message.obtain();
mHandler.sendMessageDelayed(message, 60000);
}
上面代码中有两处可能产生内存泄露:
1).匿名的thread进行耗时操作时,如果MainActivity被销毁而thread中的耗时操作没有结束的话,便会产生内存泄露。
2).匿名的handler发送消息时,如果MainActivity被销毁,而handler里面的消息还有没有发送完毕时,activity的内存也不会被回收。
解决办法:
1).继承thread实现静态内部类
2).继承handler实现静态内部类,在activity销毁时,移除所有消息mHandler.removeCallbacksAndMessages(null);
4、资源未关闭造成的内存泄露
在使用broadcast、file、cursor、stream、bitmap等资源后,没有在activity销毁时关闭或注销,那么资源就不会被回收,从而造成内存泄露。
解决办法:
在activity销毁时关闭或注销资源。
5、集合类操作的内存泄露
集合类添加元素后,仍引用着集合元素对象,导致该集合中的元素对象无法被回收,从而导致内存泄露。
static List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object obj = new Object();
objectList.add(obj);
obj = null;
}
在这个例子中,object对象放入静态集合中,因为静态变量的生命周期和应用程序一致,所以他们所引用的对象object也不能释放,这样便造成了内存泄露。
解决办法:
在集合元素使用后从集合中删除,等所有元素都使用完之后,将集合置空。
objectList.clear();
objectList = null;
6、WebView造成的内存泄露
webview一旦使用,就会造成内存泄露
解决办法:
开启一个进程,通过AIDL与主线程通信,在合适的时机销毁。
网友评论