Forver.微笑

面带微笑未必真的开心,但笑起的那一刻,心中的那些不开心的事已经不重要了~

0%

Android常见内存问题汇总

常见内存问题

通常来说,有3类内存问题

  • 内存抖动 :内存波形图呈 锯齿状、GC导致卡顿。这个问题在dalvik虚拟机上更加明显,ART上由于做了大量的优化,内存分配和GC效率提升了5~10倍,出现内存抖动的概率小很多。
  • 内存溢出 (OOM,out of memory) :是指程序在申请内存时,没有足够的内存空间供其使用,就会出现 out of memory问题。
  • 内存泄露 (memory leak) :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。memory leak 会最终会导致 out of memory!

内存分析工具

命令行

adb shell dumpsys meminfo 进程名 -d 查看当前栈中所有activity实例,如果大于理论上应该存在的Activity实例数,说明存在内存泄漏

Android profiler

3.0以前是Monitors,可以直接定位到源码
3.0之后只会生成dump文件(horof),需要自己在IDE手动按报名去分析内存泄漏。

LeakCanary原理

  • 通过传入的Context,获取applicationContext,通过registActivityCallBacks,监听所有Activity的生命周期,
  • 监听onDestory,将当前引用存到一个弱引用中,
  • 执行一个ensureGone的方法,移除所有弱引用,调用GC,如果弱引用还存在,生成当前的dump文件
  • 开启前台服务,分析dump文件,生成相应的内存泄漏信息

优点:使用简单。缺点:自身有一些bug,导致信息不准确,或者无法分析。

LeakCanary自定义用法

除了基本使用外,还可以自定义处理结果。就能实现将丰富的内存泄漏信息保存本地,或者上传到服务器进行分析。
实现步骤为

  • 定义一个类继承自DisplayLeakServcie,重写afterDefaultHandling方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class LeakCnaryService extends DisplayLeakServcie {

    private final String TAG = “LeakCanaryService”;

    @Override
    protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
    ...
    }
    }

三个参数的定义如下:
heapDump:堆内存文件,可以拿到完成的hprof文件,以使用MAT分析。
result:监控到的内存状态,如是否泄漏等。
leakInfo:leak trace详细信息,除了内存泄漏对象,还有设备信息。

  • LeakCnaryService需要在AndroidManifest清单文件中注册
  • install时,使用代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class BaseApplication extends Application {

    @Override
    public void onCreate() {
    super.onCreate();
    mRefWatcher = LeakCanary.install(this, LeakCanaryService.calss, AndroidExcludedRefs.createAppDefaults().build());
    }

    ...

    }

MAT

自己下载,早期是Eclipse的插件工具,优点:内容泄漏信息非常详细,可以分析OOM,缺点:步骤繁琐

  • 生成2个horof文件(页面进入前和后各一个,最好开关5次前后取,可以用Android profiler生成)
  • 将文件导入到MAT中,先选中Overview选项卡,再点击Histogram。再点击Histogram选项卡,右键点击Histogram按钮,把2个文件加入对比。
  • 产生一个compare tables选项卡,通常分析的是byte[], 选中bytes[]-》右键-》table2-》show objects by class
  • 生成一个新的class references选项卡,通常分析BitMap
  • 选中-》右键Merge。。。Gc Root,倒数第2个,生成非常详细的内存泄漏点

还有一个常用选项卡dominator_tree,待研究

内存溢出

OOM的场景有:

  1. 数据量大的数组
  2. 读取大文件,比如大图片。

内存泄漏的常见原因

导致Android内存泄漏的原因主要有如下几类

资源类对象不使用时忘记调用API释放

  • Bitmap忘记调用recycle()
  • Closable子类忘记调用Close()方法
  • Timer类忘记调用cancle()方法
  • 属性动画忘记调用cancle()方法

解决方法:调用对应的API释放资源

注册对象未销毁

  • BroadcastReceiver
  • EventBus

解决办法:调用反注册API

静态变量持有大数据对象

解决方法:尽量使用数据库,文件存储等方式存储数据

单例持有Context引起的内存泄漏

解决方法:优先使用Application的Context,如使用Activity的Context,最好使用弱引用封装。使用时如果获取不到,直接return

内部类引起的内存泄漏

非静态内部类匿名内部类持有默认持有外部类,它们的生命周期比外部类长。外部类对象生命周期结束时,外部类对象仍被引用不能被回收。
解决方法分2种:

  • Handle
    • 静态内部类+弱引用 或者 在外部类定义为平级的类
    • handler对象在对其引用的类生命周期结束时调用removeCallbacks方法
  • Thread
    • 静态内部类+弱引用 或者 在外部类定义为平级的类

      匿名内部类引起的内存泄漏

      原因和非静态内部类导致的内存泄漏一样
      解决方法:使用静态内部类,或者在类外部创建单独的类

容器对象没有清理造成的内存泄漏

解决方法:退出程序前需要将容器的内容清空,置为null,再退出

WebView引起的内存泄漏

解决方法:

  • 销毁webview时使用如下代码实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    protected void onDestroy() {
    super.onDestroy();
    // 先从父控件中移除WebView
    mWebViewContainer.removeView(mWebView);
    mWebView.stopLoading();
    mWebView.getSettings().setJavaScriptEnabled(false);
    mWebView.clearHistory();
    mWebView.removeAllViews();
    mWebView.destroy();
    }
  • 内存泄漏也可能是由于前端代码原因造成的,为WebView使用单独的进程,避免webview引起的内存泄漏导致应用的其他功能受到影响。

内存抖动

内存抖动导致OOM的原因

  1. 频繁创建对象,导致内存不足或碎片
  2. 不连续的内存片无法被分配,导致OOM

内存抖动复现方法

点击按钮使用handler发送一个空消息,handleMessage 接收到消息后创建内存抖动,即在 for 循环创建 100个容量为10万 的 strings 数组并在 30ms 后继续发送空消息。
一般使用 Memory Profiler (表现为 频繁GC、内存曲线呈锯齿状)结合代码排查即可找到内存抖动出现的地方。
通常的技巧就是着重查看 循环或频繁被调用 的地方。

内存抖动常见场景

字符串使用加号拼接

解决方法:

  1. 使用StringBuffer代替
  2. 初始化时设置容量,减少StringBuffer的扩容

资源复用

解决方法:

  1. 使用全局缓存池,以重用申请和释放的对象
  2. 注意结束使用后,需要手动释放对象池中的对象

减少不合理的对象创建

  1. onDraw、getView中创建的对象尽量复用
  2. 避免在循环中不断的创建局部变量

使用合理的数据结构

使用SparseArray类族、ArrayMap来替代HashMap

参考: