Android性能优化之内存泄漏

标签: android 性能优化 内存泄漏 | 发表时间:2016-12-11 16:43 | 作者:ljd2038
出处:http://blog.csdn.net

综述

  内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存。那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况。在应用中内出现一次两次的内存泄漏获取不会出现什么影响,但是在应用长时间使用以后,若是存在大量的Activity无法被GC回收的话,最终会导致OOM的出现。那么我们在这就来分析一下导致内存泄漏的常见因素并且如何去检测内存泄漏。

导致内存泄漏的常见因素

情景一:静态Activity和View

  静态变量Activity和View会导致内存泄漏,在下面这段代码中对Activity的Context和TextView设置为静态对象,从而产生内存泄漏。

  import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static Context context;
    private static TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this;
        textView = new TextView(this);
    }
}

情景二:Thread,匿名类,内部类

  在下面这段代码中存在一个非静态的匿名类对象Thread,会隐式持有一个外部类的引用LeakActivity,从而导致内存泄漏。同理,若是这个Thread作为LeakActivity的内部类而不是匿名内部类,他同样会持有外部类的引用而导致内存泄漏。在这里只需要将为Thread匿名类定义成静态的内部类即可(静态的内部类不会持有外部类的一个隐式引用)。

  public class LeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        leakFun();
    }

    private void leakFun(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

情景三:动画

  在属性动画中有一类无限循环动画,如果在Activity中播放这类动画并且在onDestroy中去停止动画,那么这个动画将会一直播放下去,这时候Activity会被View所持有,从而导致Activity无法被释放。解决此类问题则是需要早Activity中onDestroy去去调用objectAnimator.cancel()来停止动画。

  public class LeakActivity extends AppCompatActivity {

    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        textView = (TextView)findViewById(R.id.text_view);
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
        objectAnimator.start();
    }
}

情景四:Handler

  对于Handler的内存泄漏在(Android的消息机制——Handler的工作过程)[ http://blog.csdn.net/ljd2038/article/details/50889754]这篇文章中已经详细介绍,就不在赘述。

情景五:第三方库使用不当

  对于EventBus,RxJava等一些第三开源框架的使用,若是在Activity销毁之前没有进行解除订阅将会导致内存泄漏。

使用MAT检测内存泄漏

  对于常见的内存泄露进行介绍完以后,在这里再看一下使用MAT(Memory Analysis Tool)来检测内存泄露。MAT的下载地址为: http://www.eclipse.org/mat/downloads.php
  下面来看一段会导致内存泄露的错误代码。

  public class LeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        EventBus.getDefault().register(this);
    }

    @Subscribe
    public void subscriber(String s){

    }
}

  在上面这段代码中有会导致内存泄漏,原因是EventBus没有解除注册。下面就以这段代码为例来看一下如何分析内存泄漏。
  打开AndroidStudio中的Monitors可以看到如下界面。
这里写图片描述
  
  在这里可以看到在应用刚启动的时候,所占用的内存为15M,然后我们现在开始操作APP,反复进入退出LeakActicity。点击上如中的GC按钮。这时候我们在看一下内存使用情况。
这里写图片描述
  在这里我们可以看到,内存一直在持续增加,已经达到33M,并且无法被GC所回收。所以我们可以判断,这时候必然出现内存泄漏的情形。那么现在再点击Dump Java Heap按钮,在captures窗口看到生成得hprof文件。但这时候所生成的hprof文件不是标准格式的,我们需要通过SDK所提供的工具hprof-conv进行转化,该工具在SDK的platform-tools目录下。执行命令如下:

    hprof-conv XXX.hprof converted-dump.hprof

  当然在AndroidStudio中可以省去这一步,可以直接导出标准格式的hprof文件。
这里写图片描述
  
  这时候可以通过MAT工具来打开导出的hprof文件。打开界面如下图所示:
这里写图片描述
  在MAT中我们最常用的就是Histogram和Dominator Tree,他们分别对应上图中的A和B按钮。Histogram可以看出内存中不同类型的buffer的数量和占用内存的大小,而Dominator Tree则是把内存中的对象按照从大到小的顺序进行排序,并且可以分析对象之间的引用关系。在这里再来介绍一下MAT中两个符号的含义。

  • ShallowHeap:对象自身占用的内存大小,不包括他引用的对象
  • RetainedHeap:对象自身占用的内存大小并且加上它直接或者间接引用对象的大小

Histogram

   由于在Android中一般内存泄漏大多出现在Acivity中,这时候可以点击Histogram按钮,并搜索
Activity。
这里写图片描述

   在这里可以看出LeakActivity存在69个对象,基本上可以断定存在内存泄漏的情形,这时候便可以通过查看GC对象的引用链来进行分析。点击鼠标右键选择Merge Shortest paths to GC Roots并选择exclude weak/soft references来排除弱引用和软引用。
这里写图片描述
  
   在排除软引用和弱引用以后如下图所示:
这里写图片描述
   在这里可以看出由于EventBus导致的LeakActivity内存泄漏。
   在Histogram中还可以查看一个对象包含了那些对象的引用。例如,现在要查看LeakActivity所包含的引用,可以点击鼠标右键,选择list objects中的with incoming reference。而with outcoming reference表示选中对象持有那些对象的引用。
这里写图片描述

Dominator Tree

   现在我们点击这时候可以点击Dominator Tree按钮,并搜索
Activity。可以看到如下图所示:
这里写图片描述
  
   在这里可以看到存在大量的LeakActivity。然后点击鼠标右键选择Path To GC Roots->exclude weak/soft references来排除弱引用和软引用。
这里写图片描述
  
   之后可以看到如下结果,依然是EventBus导致的内存泄漏:
这里写图片描述

总结

  内存泄漏往往被我们所忽略,但是当大量的内存泄漏以后导致OOM。它所造成的影响也是不容小觑的。当然除了上述内存泄漏的分析以为我们还可以通过 LeakCanary来分析内存泄漏。对于LeakCanary的使用在这里就不在进行详细介绍。

作者:ljd2038 发表于2016/12/11 16:34:40 原文链接
阅读:1 评论:0 查看评论

相关 [android 性能优化 内存泄漏] 推荐:

Android性能优化之内存泄漏

- - CSDN博客推荐文章
  内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存. 那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况. 在应用中内出现一次两次的内存泄漏获取不会出现什么影响,但是在应用长时间使用以后,若是存在大量的Activity无法被GC回收的话,最终会导致OOM的出现.

Android性能优化之常见的内存泄漏

- - CSDN博客推荐文章
最近腾讯bugly也推出了三篇关于Android内存泄漏调优的文章,有兴趣的可以看看:. 1、 内存泄露从入门到精通三部曲之基础知识篇. 2、 内存泄露从入门到精通三部曲之排查方法篇. 3、 内存泄露从入门到精通三部曲之常见原因与用户实践. 当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏.

Android 解析内存泄漏

- - CSDN博客移动开发推荐文章
1、引用没释放造成的内存泄露.        1.1、注册没取消造成的内存泄露.        这种 Android的内存泄露比纯 Java的内存泄露还要严重,因为其他一些Android程序可能引用我们的Anroid程序的对象(比如注册机制). 即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收.

Android内存泄漏检测-LeakCanary

- - CSDN博客推荐文章
添加LeakCanary依赖包. 在主模块app下的build.gradle下添加如下依赖:. 添加Application子类. 首先创建一个ExampleApplication,该类继承于Application,在该类的onCreate方法中添加如下代码开启LeakCanary监控:. 在配置文件中注册ExampleApplication.

Android 轻松解决内存泄漏

- - IT瘾-jianshu
方法区(non-heap):编译时就分配好,在程序整个运行期间都存在. 栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存;. 堆区(heap):通常用来存放 new 出来的对象. Java四种不同的引用类型. 强引用(Strong Reference):JVM 宁愿抛出 OOM,也不会让 GC 回收存在强引用的对象.

Android 性能优化

- - CSDN博客综合推荐文章
如果应用程序需要使用Service来执行后台任务的话,只有当任务正在执行的时候才应该让Service运行起来. 当启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,系统可以在LRUcache当中缓存的进程数量也会减少,导致切换程序的时候耗费更多性能. 我们可以使用IntentService,当后台任务执行结束后会自动停止,避免了Service的内存泄漏.

Android内存泄漏产生的6大原因

- - IT瘾-geek
1.资源对象没关闭造成的内存泄漏. 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存. 它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外. 如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏.

Android内存泄漏思考 - 编程学习网

- -
Android内存泄漏是一个经常要遇到的问题,程序在内存泄漏的时候很容易导致OOM的发生. 那么如何查找内存泄漏和避免内存泄漏就是需要知晓的一个问题,首先我们需要知道一些基础知识. 强引用: 强引用是Java中最普通的引用,随意创建一个对象然后在其他的地方引用一下,就是强引用,强引用的对象Java宁愿OOM也不会回收他.

Android性能优化典范

- - 移动开发 - ITeye博客
2015新年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App. 课程专题不仅仅介绍了Android系统中有关性能问题的底层工作原理,同时也介绍了如何通过工具来找出性能问题以及提升性能的建议. 主要从三个方面展开,Android的渲染机制,内存与GC,电量优化.

Android 性能篇 -- 带你领略Android内存泄漏的前世今生

- - SegmentFault 最新的文章
内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗. 内存泄漏并不是指物理上的内存消失,这里的内存泄漏是指由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费. Java 程序运行时的内存分配策略有三种,分别是. 静态分配 、 栈式分配 和. 堆式分配 ,对应的三种存储策略使用的内存空间主要分别是.