• Android内存泄露总结


    内存泄露是如何产生的?

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

      内存泄露是造成OOM的主要原因之一。

    内存泄露的对象是什么?

      内存分配有三种策略:静态(静态存储区/方法区)、栈、堆。

        静态存储区(方法区):内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局static数据和常量。

          栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

          堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存(Java则依赖垃圾回收器)。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。

      堆和栈的区别:

        栈:定义一些基本数据类型变量和应用变量。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

        堆:用于存放所有由new创建的对象(内容包括该对象其中的所有成员变量)和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

        堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit系统理论上是4G),所以堆的空间比较灵活,比较大。栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在编译时确定,VC中可设置)。

        对于堆,频繁的new/delete会造成大量内存碎片,使程序效率降低。对于栈,它是先进后出的队列,进出一一对应,不产生碎片,运行效率稳定高。

      结论:

        局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。

        ——因为它们属于方法中的变量,生命周期随方法而结束。

        成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)

        ——因为它们属于类,类对象终究是要被new出来使用的。

    内存为什么会泄露?

      Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。

      堆内存中的长生命周期的对象持有短生命周期对象的强/软引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的根本原因。

    Java内存回收机制:

      从程序的主要运行对象(如静态对象/寄存器/栈上指向的堆内存对象等)开始检查引用链,当遍历一遍后得到上述这些无法回收的对象和他们所引用的对象链,组成无法回收的对象集合,而其他孤立对象(集)就作为垃圾回收。GC为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。

      关于“引用”:通俗的讲,通过A能调用并访问到B,那就说明A持有B的引用,或A就是B的引用。

      Java中对引用的分类:

      

      在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

      软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。

    常见的内存泄露情况:

    1、集合类泄露

      集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。

    2、非静态内部类创建静态实例造成内存泄漏。

      修复方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,推荐的使用Application 的 Context。

      匿名内部类有可能会持有当前Activity实例,如果将这个引用传入到异步线程,此线程和Activity生命周期不一致是,将会造成内存泄露。

    3、Handler 造成的内存泄漏

      Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有,且Handler的声明周期和Activity的不一致,容易导致内存无法被正确释放。

      修复方法:在Activity中避免使用非静态内部类,即将Handler改为static,此时Handler的存活周期将于Activity无关,同事以弱应用的方式引入Activity,避免直接将Activity作为context传入。每次使用前需判空。 

      Looper 线程的消息队列中还是可能会有待处理的消息,所以我们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。

    public class MainActivity extends AppCompatActivity {
        private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
        private static class MyHandler extends Handler {
            private WeakReference reference;
            public MyHandler(Context context) {
            reference = new WeakReference<>(context);
            }
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = (MainActivity) reference.get();
                if(activity != null){
                activity.mTextView.setText("");
                }
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mTextView = (TextView)findViewById(R.id.textview);
            loadData();
        }
    
        private void loadData() {
            //...request
            Message message = Message.obtain();
            mHandler.sendMessage(message);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacksAndMessages(null);
        }
    }

    4、尽量避免使用 static 成员变量

      如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。将会导致app常驻内存,从而导致app频繁重启,造成耗电、耗流量。

      修复方法:不要在类初始时初始化静态成员。可以考虑lazy初始化。

    5、避免 override finalize()

      finalize 方法被执行的时间不确定,finalize 方法只会被执行一次,即使对象被复活,如果已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,含有Finalize方法的object需要至少经过两轮GC才有可能被释放。

    6、线程泄露

      线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。  

    6、资源未关闭造成的内存泄漏

      应在Activity被销毁时及时关闭或注销BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源。

    7、在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。

    8、单例造成的内存泄露

      由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,则导致这个对象不能被正常回收,造成内存泄露。

      解决办法:

    public class AppManager {
         private static AppManager instance;
         private Context context;
         private AppManager(Context context) {
               this.context = context.getApplicationContext();// 使用Application 的context
         }
         public static AppManager getInstance(Context context) {
              if (instance != null) {
                    instance = new AppManager(context);
              }
              return instance;
        }
    }

      或者以下写法,连Context都不用传进来了

    ...
    
    context = getApplicationContext();
    
    ...
    /**
    * 获取全局的context
    * @return 返回全局context对象
    */
    public static Context getContext(){
         return context;
    }
    
    public class AppManager {
         private static AppManager instance;
         private Context context;
         private AppManager() {
               this.context = MyApplication.getContext();// 使用Application 的context
         }
         public static AppManager getInstance() {
              if (instance != null) {
                     instance = new AppManager();
              }
              return instance;
         }
    }

    检测程序中内存泄露的工具:LeakCanary、MAT等

    一些建议

    1、对于生命周期比Activity长的对象如果需要应该使用ApplicationContext

    2、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏

    3、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null

    4、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期

    5、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:  

      (1)将内部类改为静态内部类

      (2)静态内部类中使用弱引用来引用外部类的成员变量

    6、在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:

    参考文献:

      http://dev.qq.com/topic/59152c9029d8be2a14b64dae

  • 相关阅读:
    qt中qmake的详解
    教程:从零开始 使用Python进行深度学习!
    win10系统下搭建Python开发环境和TensorFlow深度学习环境(CPU版)
    怎么选择视觉光源颜色
    pycharm安装及设置中文
    新建DataSet和DataTable,并从中提取数据到文本
    网站服务基础面试
    TCP、UDP数据包大小的限制
    TCP的三次握手与四次挥手理解及面试题(很全面)
    zabbix服务深入
  • 原文地址:https://www.cnblogs.com/yl-saber/p/7194394.html
Copyright © 2020-2023  润新知