Android内存问题及调试工具

一. 关于内存的几个概念

1. GC 的工做机制

当 GC 工做时,虚拟机中止其余工做。频繁地触发 GC 进行内存回收,会致使系统性能严重降低。php

2. 内存抖动

在极短的时间内,分配大量的内存,而后又释放它,这种现象就会形成内存抖动。典型地,在 View 控件的 onDraw 方法里分配大量内存,又释放大量内存,这种作法极易引发内存抖动,从而致使性能降低。由于 onDraw 里的大量内存分配和释放会给系统堆空间形成压力,触发 GC 工做去释放更多可用内存,而 GC 工做起来时,又会吃掉宝贵的帧时间 (帧时间是 16ms) ,最终致使性能问题。html

3. 内存泄露

Java 语言的内存泄漏概念和 C/C++ 不太同样,在 Java 里是指不正确地引用致使某个对象没法被 GC 释放,从而致使可用内存愈来愈少。好比,一个图片查看程序,使用一个静态 Map 实例来缓存解码出来的 Bitmap 实例来加快加载进度。这个时候就可能存在内存泄漏。java

内存泄漏会致使可用内存愈来愈少,从而致使频繁触发 GC 回收内存,进而致使性能降低。android

二. 调试工具

  • Memory Monitor: 能够查阅 GC 被触发起来的时间序列,以便观察 GC 是否影响性能。
  • Allocation Tracker: 从 Android Studio 的这个工具里查看一个函数调用栈里,是否有大量的相同类型的 Object 被分配和释放。若是有,则其可能引发性能问题。
  • MAT: 这是 Eclipse 的一个插件,也有单独的工具能够下载使用。

三. 两个简单的实例

1. 内存抖动

经过一个很是简单的例子来演示内存抖动。这个例子里,在自定义 View 的 onDraw 方法里大量分配内存来演示内存抖动和性能之间的关系。canvas

版本一:缓存

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        String msg = "";
        for (int i = 0; i < 500; i++) {
            if (i != 0) {
                msg += ", ";
            }
            msg += Integer.toString(i + 1);
        }
        Log.d("DEBUG", msg);
    }

版本二:性能优化

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 500; i ++) {
            if (i != 0) {
                sb.append(", ");
            }
            sb.append(i + 1);
        }
        Log.d("DEBUG", sb.toString());
    }

内存抖动的特征:从 Memory Monitor 来看,有毛刺出现。即短期内分配大量的内存并触发 GC。oracle


              memory_churn

从 Allocation Tracker 里看,一次操做会有大量的内存分配产生。app


              memory_tracker

2. 内存泄漏

这个例子里,咱们简单地让点击 Settings 菜单,就产生一个 100KB 的内存泄漏。dom

private void addSomeCache() {
        // add 100KB cache
        int key = new Random().nextInt(100);
        Log.d("sfox", "add cache for key " + key);
        sCache.put(key, new byte[102400]);
    }

内存泄漏的特征:从 Memory Monitor 来看,内存占用愈来愈大


              memory_tracker

利用 MAT 工具进行专业分析。这是个很大的话题。几乎能够独立成几个章节来说。能够参阅 MAT 自己自带的 Tutorials 来学习。另外,这篇文章里的分析方法是个不错的开始。

四. 利用 MAT 分析内存问题

一个典型的问题是 Android 系统越用越慢。这种典型地是由内存泄漏引发的。一个颇有用的解决这种问题的办法是:比较先后两个阶段的内存的使用状况。通常流程以下:

  • 利用 ddms 工具 dump HPROF file
  • 利用 hprof-conv 把 dalvik 格式的转换为普通 jvm 格式
  • 重复步骤 1 和 2 抓出两份 LOG。
  • 利用 MAT 对两份 HRPOF 文件进行分析,结合代码找出可能存在的内存泄漏

好比针对拨号盘愈来愈慢的问题,咱们能够开机后启动拨号盘,打进打出10个电话。而后抓个 HPROF 文件。接着,再打进打出10个电话,再抓一个 HPROF 文件。接着拿这两个文件对比分析,看是否是会形成电话打进打出越多,内存占用越多的状况发生。

HPROF文件:HPROF 简单地理解,就是从 jvm 里 dump 出来的内存和 CPU 使用状况的一个二进制文件。它的英文全名叫 A Heap/CPU Profiling Tool。这里有它完整的官方文档和它的历史介绍。

打开 MAT 后,会有一个 Tutorials 来教你们怎么用。这里列出几个操做步骤及其注意事项。

  • 在 DDMS 里导出 HPROF 文件前,最好手动执行一下 GC。目的是让导出的内存所有是被引用的。不然在作内存占用对比时,会有不少没必要要的内存占用被标识出来,干扰咱们进行分析。
  • 进行对比时,最好是选择操做较多的和操做较少的对比,这样得出的 delta 是正数
  • 经过对比,发现内存泄漏时,能够用 OQL 来查询,并经过 Root to GC 功能来找到发生泄漏的源代码

在咱们的示例程序里面,每次点击 Settings 菜单,都会致使一次100KB的内存泄漏。下面是咱们利用上面介绍的流程来查找内存泄漏问题。咱们先点击 5 次 Settings 菜单,而后手动触发一次 GC,再导出 HPROF 文件。接着,咱们再点击 6 次 Settings 菜单,而后手动触发一次 GC,再导出第二份 HPROF 文件。咱们拿这两份 HPROF 就能够作一些对比。

             mat_diff.png

经过上图能够看到,两次操做确实致使了某些类的实例增长了。图中能够清楚地看到 byte[] 和 java.util.HashMap$HashMapEntry 两个类增长得比较明显。这样,咱们随便选择一个,经过 OQL 来查询系统中的这个内存。

             mat_qql.png

从上图能够找到,本次 dump 出来的内存里,确实有不少个这个类的实例。在图上右击任何一个实例,右击,选择 Paths to GC roots ,能够找到这个实例是被谁引用的。

              mat_gc_root.png

从上图能够看出来,这个内存是被 MainActivity 里的 sCache 引用的。经过阅读代码,咱们就能够找到这个漏洞了。即每次都往 sCache 里保存一个引用。

参考资料:

  1. Android 性能优化内存篇
  2. Android性能优化典范胡凯的博客
相关文章
相关标签/搜索