美文网首页Android面试题
【知识】内存泄露

【知识】内存泄露

作者: FynnJason | 来源:发表于2019-12-26 11:05 被阅读0次

    原创不易,尊重作者,转载请注明出处

    前言

    内存泄露是指当一个对象已经不需要再使用本该回收时候,另外一个正在使用的对象持有它的引用从而导致它不能被回收,该对象就会停留在堆内存中,这就产生了内存泄露。

    内存泄露的影响

    内存泄露是造成App出现OOM的主要原因。

    检测内存泄露的方法

    借助两个工具:MATLeakCanary

    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与主线程通信,在合适的时机销毁。

    相关文章

      网友评论

        本文标题:【知识】内存泄露

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