子线程耗时操作导致内存泄漏分析
编码经常遇到这么一种情况:
public class Manager {
private Context context;
public Manager(Context context){
this.context = context;
}
//模拟子线程耗时操作
public void net(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//TODO use context
}
}).start();
}
}
使用时:
Manager manager = new Manager(this);
manager.net();
Manager持有context,会导致内存泄漏吗?
简单来讲,GC会根据引用树进行垃圾回收,当对象没有被引用时,就会被GC选中。
下面一步步开始分析:
1.假如代码是如下:
public class Manager {
private Context context;
public Manager(Context context){
this.context = context;
}
//模拟主线程非耗时操作
public void mainDoSomething(){
//TODO
}
}
引用关系如下:
589086DC-5238-4528-9B28-FACC205451ED.png
箭头指向表示被引用的对象
Activity与Manager存在循环引用,这种情况,Activity销毁时,引用树指向Activity的箭头断开,故会被GC回收,不存在内存泄漏。
2.如果是文中开头的代码呢?
它的引用关系如下:
0C72F414-A629-4907-962E-40244EF5DB3A.png要理解这个图,首先要知道:
Java中,非静态内部类、非静态匿名内部类持有对外部类的引用。 所以代码中Thread持有Manager的引用。
Java中,所有运行线程不会被回收。Activity销毁时,由于Thread不会被回收,所以它持有的Manager不会被回收,同时也就导致Manager持有的context不会被回收,导致Activity内存泄漏。
从图上看出,指向Activity的箭头有俩个,当Activity销毁时,Activity仍然被Manager引用,故会内存泄漏。
3.其实文中开头的代码,简化后就是我们常见的版本:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
//模拟子线程耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
它的引用关系如下:
F453C191-FA94-459B-AE09-6E847D247206.png可见匿名内部类线程会导致内存泄漏。
4.匿名内部类线程导致内存泄漏如何避免呢?
可以使用弱引用解决这个问题:
//创建静态类以避免对外部类引用!
public static class MyThread extends Thread {
private WeakReference<Activity> reference;
public MyThread(Activity activity) {
reference = new WeakReference<>(activity);
}
@Override
public void run() {
super.run();
Activity activity = reference.get();
if (activity != null) {
//TODO
}
}
}
//调用
MyThread myThread = new MyThread(this);
myThread.start();
5.实际代码编写中,下面情况非常常见。
比如使用Volley进行网络访问,访问成功后更改UI,代码如下:
public void net(){
String request = new StringRequest("xxx", new Response.Listener<String>() {
@Override
public void onResponse(String response) {
tv.setText(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
//访问网络...
}
new Response.Listener是匿名内部类,持有context的引用,这种代码存在内存泄漏的可能,那么是否都要改成上述写法呢?
答案是视情况而定,原因如下:
<1.Volley等框架提供了cancel方法,可以在onDestroy()里执行request.cancel()避免内存泄漏。
详情可以参考https://blog.csdn.net/aq15756005983/article/details/70230106。
Volley cancel()机制在旧版存在内存泄漏问题,好在新版已经解决。
<2.之所以会内存泄漏,是因为Thread运行时间过长,大大超出Activity存活时间所致。但是当Thread运行完毕,Activity就会被正常回收。从上述代码可以看出,增加弱引用等方式会增加代码复杂度,所以这种应对内存泄漏的写法,多用于频繁打开、且网络超时较长的页面,因为这类页面会因内存泄漏而短时间内大量占用内存。
当然,健壮的代码,应当尽可能的避免内存泄漏的发生。
以上仅个人看法。