Android内存泄漏分析以及解决方案

作者: 正阳Android | 来源:发表于2017-11-15 17:49 被阅读98次

    本文是看了公众号的文章,非常感谢,链接如下

    https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650241803&idx=1&sn=59b3a61c8a52213f8beaebb2c1417aed&chksm=88638a64bf140372a29d002168186725ee78528802c93b0f52d6a653c6683ea8bd4205291bb3&mpshare=1&scene=23&srcid=1113eXivwBI4RWZvrwJPx7T7#rd

    概念

    1.什么是内存泄漏?

            一句话总结的话,那就是生命周期长的对象持有短生命周期的对象的引用导致其无法被及时释放,就会造成内存泄漏.(内存泄漏最终会导致内存溢出)

            换句话说就是:程序不在使用的对象, 本来应该被垃圾回收期器释放,但是由于被无效的引用就会,从而导致无法被回收造成内存泄漏.

    2.关于内存分配.

    JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)

        堆区:

        1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)

       2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

      3. 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。

       栈区:

       1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中

       2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。

       3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

       4.当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上   创   建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算   内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

       方法区:

       1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

        2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

        3.这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。

    PS: 所有的成员变量都是存放于堆中,局部变量存放于栈中;

    堆与栈的区别: 存放于栈的局部变量,基本类型变量和引用变量当方法执行结束,或者说超出作用域之后会被释放,该内存空间可以被重新利用.而且栈的存取速度比堆要快.堆:堆中分配的内存是由垃圾回收器自动管理,在堆中产生了一个数组或者对象后,还可以在栈中定义一个变量,这个变量的取值就是数组或者对象在堆内存中的首地址,这个变量也称为引用变量.

    3.可能引起内存泄漏的原因

        一、 Context 引起的内存泄漏

       我们经常会用到Context,上下文的引用,如果可以建议使用ApplicationContext,当然有些场景下我们必须使用Activity,而不能使用ApplicationContext;,此种场景下我们必须在界面销毁的时候手动释放掉context.

       例如我们使用单例,在单例里面我们会使用Context,若我们使用的不是ApplicationContext,那么当Activity退出的时候由于其被引用,导致无法被回收就会造成内存泄漏,所以此时使用ApplictionContext就可以解决问题.因为ApplicationContext的生命周期和单例的生命周期是一致的.

    Context的使用方法

    二,内部类持有外部类的引用

       非静态内部类和匿名类都会潜在引用他们所属的外部类,(静态内部类不会),所以如果非静态内部类持有外部类的引用,若这个非静态内部类的生命周期比外部类生命周期长(例如处理耗时操作,而activity生命周期已经结束),那么当我们销毁外部类的时候,由于内部类还持有其引用,那么就会造成无法被回收.

    三,外部类持有非静态内部类的的静态对象.(静态成员引起的内存泄漏)

    public class MainActivity extends AppCompatActivity {

    private static Test test;

         @Override

    protected void onCreate(Bundle savedInstanceState) {

             super.onCreate(savedInstanceState);

            setContentView(R.layout.activity_main);

           if (test == null) {

           test = new Test();

               }

           }

         private class Test {

           }

    }

    如上图所示,静态对象test的生命周期和应用的生命周期是一致的,而类Test是非静态内部类,其持有外部类MainActivity的引用,导致MainActivity退出的时候无法被回收,;这里把test修改成非静态的,或者是Test修改成static的都可以解决内存泄漏的问题.

    四,Handler 或 Runnable 作为非静态内部类

    handler 和 runnable 都有定时器的功能,当它们作为非静态内部类的时候,同样会持有外部类的引用,如果它们的内部有延迟操作,在延迟操作还没有发生的时候,销毁了外部类,那么外部类对象无法回收,从而造成内存泄漏.

    public class MainActivity extends AppCompatActivity {

          @Override

    protected void onCreate(Bundle savedInstanceState) {

                super.onCreate(savedInstanceState);

                setContentView(R.layout.activity_main);

               new Handler().postDelayed(new Runnable() {

                    @Override

                   public void run() {

              }

                   }, 10 * 1000);

               }

    }

    上面的代码中,Handler 和 Runnable 作为匿名内部类,都会持有 MainActivity 的引用,而它们内部有一个 10 秒钟的定时器,如果在打开 MainActivity 的 10 秒内关闭了 MainActivity,那么由于 Handler 和 Runnable 的生命周期比 MainActivity 长,会导致 MainActivity 无法被回收,从而造成内存泄漏。

    那么应该如何避免内存泄漏呢?这里的一般套路就是把 Handler 和 Runnable 定义为静态内部类,这样它们就不再持有 MainActivity 的引用了,从而避免了内存泄漏:

    public class MainActivity extends AppCompatActivity {

    private Handler handler;

    private Runnable runnable;

       @Override

    protected void onCreate(Bundle savedInstanceState) {

                 super.onCreate(savedInstanceState);

                 setContentView(R.layout.activity_main);

                  handler = new TestHandler();

                 runnable = new TestRunnable();

                   handler.postDelayed(runnable, 10 * 1000);

    }

             private static class TestHandler extends Handler {

    }

              private static class TestRunnable implements Runnable {

              @Override

                   public void run() {

         Log.d(TAG, "run: ");

        }

    }

         private static final String TAG = "MainActivity";

    }

    最好再在 onDestory 调用 handler 的 removeCallbacks 方法来移除 Message,这样不但能避免内存泄漏,而且在退出 Activity 时取消了定时器,保证 10 秒以后也不会执行 run 方法:

    @Override

    protected void onDestroy() {

    super.onDestroy();

    handler.removeCallbacks(runnable);

    }

    上面的代码,使用 leakcanary 工具会发现依然会发生内存泄漏,而且造成内存泄漏的原因和之前用非静态内部类是一样的,那么为什么会出现这种情况呢?这是由于在 Handler 中持有 Context 对象,而这个 Context 对象是通过 TestHandler 的构造方法传入的,它是一个 MainActivity 对象,也就是说,虽然 TestHandler 作为静态内部类不会持有外部类 MainActivity 的引用,但是我们在调用它的构造方法时,自己传入了 MainActivity 的对象,从而 handler 对象持有了 MainActivity 的引用,handler 的生命周期比 MainActivity 的生命周期长,因此会造成内存泄漏,这种情况可以使用弱引用的方式来引用 Context 来避免内存泄漏,代码如下:

    public class MainActivity extends AppCompatActivity {

    private Handler handler;

    private Runnable runnable;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    handler = new TestHandler(new WeakReference(this));

    runnable = new TestRunnable();

    handler.postDelayed(runnable, 10 * 1000);

    }

    private static class TestHandler extends Handler {

    private Context context;

    private TestHandler(WeakReference weakContext) {

    context = weakContext.get();

    }

    }

    private static class TestRunnable implements Runnable {

    @Override

    public void run() {

    Log.d(TAG, "run: ");

    }

    }

    private static final String TAG = "MainActivity";

    }

    其他引起内存泄漏的地方:

    1.广播注册之后,没有在activity的onDestroy方法中取消注册

    2.file的读写,没有关闭流

    3. Bitmap使用后没有调用recycle();

    相关文章

      网友评论

        本文标题:Android内存泄漏分析以及解决方案

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