• 内存泄漏解决方案


    “内存泄漏”就是一个对象已经不需要再使用了,但是因为其它的对象持有该对象的引用,导致它的内存不能被回收。“内存泄漏”的慢慢积累,最终会导致OOM “内存溢出”的发生,千里之堤,毁于蚁穴。所以在写代码的过程中,应该要注意规避会导致“内存泄漏”的代码写法,提高软件的健壮性。

    一、常见导致“内存泄漏”的代码写法及解决方案

    1.静态变量引起的内存泄漏

    在java中静态变量的生命周期是在类加载时开始,类卸载时结束。换句话说,在android中其生命周期是在进程启动时开始,进程死亡时结束。所以在程序的运行期间,如果进程没有被杀死,静态变量就会一直存在,不会被回收掉。如果静态变量强引用了某个Activity中变量,那么这个Activity就同样也不会被释放,即便是该Activity执行了onDestroy(不要将执行onDestroy和被回收划等号)。

    这类问题的解决方案为:1.寻找与该静态变量生命周期差不多的替代对象。2.若找不到,将强引用方式改成弱引用。

    比较典型的例子如下:
    单例引起的Context内存泄漏

    public class IMManager {
      private Context context;
      private static IMManager mInstance;
    
      public static IMManager getInstance(Context context) {
        if (mInstance == null) {
          synchronized (IMManager.class) {
            if (mInstance == null)
              mInstance = new IMManager(context);
          }
        }
        return mInstance;
      }
    
      private IMManager(Context context) {
        this.context = context;
      }
    
    }

    当调用getInstance时,如果传入的context是Activity的context。只要这个单例没有被释放,这个Activity也不会被释放。

    解决方案

    传入Application的context,因为Application的context的生命周期比Activity长,可以理解为Application的context与单例的生命周期一样长,传入它是最合适的。

    public class IMManager {
      private Context context;
      private static IMManager mInstance;
    
      public static IMManager getInstance(Context context) {
        if (mInstance == null) {
          synchronized (IMManager.class) {
            if (mInstance == null)
              //将传入的context转换成Application的context
              mInstance = new IMManager(context.getApplicationContext());
          }
        }
        return mInstance;
      }
    
      private IMManager(Context context) {
        this.context = context;
      }
    
    }

    2、非静态内部类引起的内存泄漏

    在java中,创建一个非静态的内部类实例,就会引用它的外围实例。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。

    这类问题的解决方案为:1.将内部类变成静态内部类 2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用。3.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务。

    内部线程造成的内存泄漏

    public class LeakAty extends Activity {
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_leak);
        test();
      }
    
      public void test() {
        //匿名内部类会引用其外围实例LeakAty.this,所以会导致内存泄漏
        new Thread(new Runnable() {
    
          @Override
          public void run() {
            while (true) {
              try {
                Thread.sleep(1000);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
            }
          }
        }).start();
      }
      }

    解决方案

    将非静态匿名内部类修改为静态匿名内部类

    public class LeakAty extends Activity {
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_leak);
        test();
      }
      //加上static,变成静态匿名内部类
      public static void test() {
        new Thread(new Runnable() {
    
          @Override
          public void run() {
            while (true) {
              try {
                Thread.sleep(1000);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
            }
          }
        }).start();
      }
    }

    Handler引起的内存泄漏

    public class LeakAty extends Activity {
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_leak);
        fetchData();
    
      }
    
      private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
          switch (msg.what) {
          case 0:
            // 刷新数据
            break;
          default:
            break;
          }
    
        };
      };
    
      private void fetchData() {
        //获取数据
        mHandler.sendEmptyMessage(0);
      }
    }

    mHandler 为匿名内部类实例,会引用外围对象LeakAty.this,如果该Handler在Activity退出时依然还有消息需要处理,那么这个Activity就不会被回收。

    解决方案

    public class LeakAty extends Activity {
      private TextView tvResult;
      private MyHandler handler;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_leak);
        tvResult = (TextView) findViewById(R.id.tvResult);
        handler = new MyHandler(this);
        fetchData();
    
      }
      //第一步,将Handler改成静态内部类。
      private static class MyHandler extends Handler {
        //第二步,将需要引用Activity的地方,改成弱引用。
        private WeakReference<LeakAty> atyInstance;
        public MyHandler(LeakAty aty) {
          this.atyInstance = new WeakReference<LeakAty>(aty);
        }
    
        @Override
        public void handleMessage(Message msg) {
          super.handleMessage(msg);
          LeakAty aty = atyInstance == null ? null : atyInstance.get();
          //如果Activity被释放回收了,则不处理这些消息
          if (aty == null||aty.isFinishing()) {
            return;
          }
          aty.tvResult.setText("fetch data success");
        }
      }
    
      private void fetchData() {
        // 获取数据
        handler.sendEmptyMessage(0);
      }
    
      @Override
      protected void onDestroy() {
        //第三步,在Activity退出的时候移除回调
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
      }
    }

    3、资源未关闭引起的内存泄漏
    当使用了BraodcastReceiver、File、Cursor、Bitmap等资源时,当不需要使用时,需要及时释放掉,若没有释放,则会引起内存泄漏。

    4、构造Adapter时,没有使用缓存的 convertView

    以构造ListView的BaseAdapter为例,在BaseAdapter中提共了方法:

    public View getView(intposition, View convertView, ViewGroup parent)

    来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。

    由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支。

    5、一些不良代码成内存压力

    有些代码并不造成内存泄露,但是它们或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,增加vm的负担,造成不必要的内存开支。

    如Bitmap使用不当

    第一、及时的销毁。

    虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。

    第二、设置一定的采样率。

    有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:

    private ImageView preview;
    BitmapFactory.Options options = newBitmapFactory.Options();
    options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
    Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
    preview.setImageBitmap(bitmap);   

    总结

    • 对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。
    • 尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
    • 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
      • 将内部类改为静态内部类
      • 静态内部类中使用弱引用来引用外部类的成员变量
    • Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.
    • 在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
    • 正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
    • 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
  • 相关阅读:
    torch.nn.functional中softmax的作用及其参数说明
    from __future__ import包的作用
    python textwrap的使用
    深度学习框架PyTorch一书的学习-第五章-常用工具模块
    python实现命令行解析的argparse的使用
    pytorch visdom可视化工具学习—2—详细使用-1—基本使用函数
    pytorch visdom可视化工具学习—2—详细使用-3-Generic Plots和Others
    pytorch visdom可视化工具学习—2—详细使用-2-plotting绘图
    硬盘没有显示
    Eclipse中syso 快捷键 Alt + / 不能使用的问题
  • 原文地址:https://www.cnblogs.com/labixiaoxin/p/5239215.html
Copyright © 2020-2023  润新知