美文网首页
子线程耗时操作导致内存泄漏分析

子线程耗时操作导致内存泄漏分析

作者: Parallel_Lines | 来源:发表于2018-09-03 15:41 被阅读0次

    编码经常遇到这么一种情况:

    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就会被正常回收。从上述代码可以看出,增加弱引用等方式会增加代码复杂度,所以这种应对内存泄漏的写法,多用于频繁打开、且网络超时较长的页面,因为这类页面会因内存泄漏而短时间内大量占用内存。

    当然,健壮的代码,应当尽可能的避免内存泄漏的发生。

    以上仅个人看法。

    相关文章

      网友评论

          本文标题:子线程耗时操作导致内存泄漏分析

          本文链接:https://www.haomeiwen.com/subject/jpxiwftx.html