Android应用程序内存分析
原文链接:http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html
Dalvik 自带有内存回收机制,但这并不意味程序员可以忽略内存的管理。在移动设备上,内存比较紧缺,因此你应当对内存的使用更加小心。在本文中,我们将介绍如何使用android SDK中的内存分析工具来对应用程序的内存使用情况进行分析。
有些内存问题是显而易见的。例如,如果应用程序在每次用户触摸屏幕时都会泄漏一些内存,那么该应用将很快因为 OutOfMemoryError的异常而崩溃。另外一些则更加隐蔽,它们有可能只会导致应用程序和整个系统变慢(垃圾回收器频繁和长时间进行垃圾回收)
调试工具
在android SDK中提供了两种内存分析的方法:内存申请跟踪(以下用Allocation tracker) 和 堆转储(以下用heap dump)。Allocation tracker可以帮我们了解到一段时间内,应用对内存的申请情况,但是它不能提供任何有关应用程序堆的信息。更多有关内存申请追踪的信息请看 Tracking Memory Allocations 。接下来的篇幅中我们将重点关注一种更加强有力的分析工具——heap dump的使用。
Heap dump是应用程序堆的的快照,它存放在一种特定格式(HPROF)的二进制文件中。Dalvik使用类似于HPROF的格式来保存heap dump。生成应用程序的heap dump的方法有多种,DDMS中的head dump按钮就提供了生成heap dump的功能。如果想要对heap dump的生成有更加精细的控制,可以在程序中使用android.os.Debug.dumpHprofData()来生成。
常用的分析heap dump的工具有jhat 和 Eclipse Memory Analyzer (MAT) 。在分析之前,我们需要将Dalivk heap dump文件的格式转换成J2SE 标准的HPROF格式。android SDK中提供了hprof-conv工具来进行这个转换。命令如下:
hprof-conv dump.hprof converted-dump.hprof
例子:如何分析内存泄漏
在Dalivk环境中,程序员不能显式的申请和释放内存,因此内存不会像在C/C++中的那样真正泄漏。在这种情况下,内存泄漏往往指的是程序中保存了无用的对象的引用,有时保存一个对象的引用,而这个对象又拥有大量其他对象的引用,那么这些对象将都无法被垃圾回收器回收。
让我们看android SDK中的一个示例程序Honeycomb Gallery sample app。这是一个用来展示如何使用android Honeycomb 中的新API的照片库应用(如何下载和编译这个应用的代码,请看介绍)。我们将小心的在这个应用的代码中加入一个内存泄漏的bug,以讲述如何调试这样的问题。
假如我们想修改这个应用,让其具有从网络上下载图片的能力。为了提升体验,我们决定实现一个缓存以存储最近收到的图片,我们通过对ContentFragment.java文件进行一些小的修改来实现这个想法。首先在这个类的顶部加上一个静态的变量
private static HashMap<String,Bitmap> sBitmapCache = new HashMap<String,Bitmap>();
这个变量将保存我们想要缓存的图片,现在我们修改updateContentAndRecycleBitmap() 加入检查存储和存入缓存的功能
void updateContentAndRecycleBitmap(int category, int position) {
if (mCurrentActionMode != null) {
mCurrentActionMode.finish();
}
// Get the bitmap that needs to be drawn and update the ImageView.
// Check if the Bitmap is already in the cache
String bitmapId = "" + category + "." + position;
mBitmap = sBitmapCache.get(bitmapId);
if (mBitmap == null) {
// It's not in the cache, so load the Bitmap and add it to the cache.
// DANGER! We add items to this cache without ever removing any.
mBitmap = Directory.getCategory(category).getEntry(position)
.getBitmap(getResources());
sBitmapCache.put(bitmapId, mBitmap);
}
((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap);
}
这里,我故意留下了一个memory leak的问题:我们将Bitmap添加到cache但是却从来不从cache中删除它们。在真正的应用中,往往会对cache的大小进行一个限制。
使用DDMS来分析内存使用情况
DDMS是android最主要的一个调试工具,它是ADT插件的一部分,同时在android SDK的tools/ directory目录下也有一个独立的版本。更对关于DDMS的信息,请看Using DDMS
让我们使用DDMS来分析内存使用情况。DDMS有两种启动方式:
- 从Eclipse:点击Window > Open Perspective > Other... > DDMS
- 从命令行:在tools/ directory中执行ddms命令
在左边的面板里选择进程com.example.android.hcgallery,然后点击工具栏上的Show heap updates按钮,之后切换到DDMS中的VM Heap页。这里显示了堆内存的基本状态信息,这些信息每次GC时更新。点击Cause GC按钮可以触发第一次更新。
可以看到,我们使用了8MB多一点的内存,现在滑动相册,看到这个数字会变大。由于应用中总共只有13張图片,因此我们不会无限制的泄漏内存。从某种意义上来说,这是一种最糟糕的泄漏,因为我们永远也不会收到OutOfMemoryError的异常来帮助我们发现这个问题。
创建一个heap dump
让我们使用heap dump来定位这个问题。点击中DDMS工具栏上的Dump HPROF按钮,选择一个保存路径,然后运行hprof-conv处理这个文件。在这个例子中,我将使用MAT的独立应用版本(版本号1.0.1),下载链接MAT download site 如果你正在使用ADT并且Eclipse中已经安装了MAT插件,点击dump HPROF按钮,工具将会自动对HPROF文件进行格式转换,然后直接在Eclipse中使用MAT打开转换后的HPROF文件。
使用MAT分析heap dump
启动MAT,打开我们转换过的HPROF文件。MAT是个强大的工具,拥有很多功能。在这篇文章中,我只介绍一种使用它来分析内存问题的方法:Histogram view。Histogram view显示了堆中所有类的列表,我们可以将列表中这些类按照其实例(object)的个数的多少排序(包括按照浅堆中实例的个数和深堆中实例的个数)
如果我们按照浅堆排序,我们可以看到byte[]类型排在第一位,这是因为在android3.0中bitmap的像素点数据都保存在byte数组中(之前它们都没有被保存在dalik的堆空间中),基于这些数组的大小,我们可以断言,这些数组就是我们泄漏的bitmap内存的一部分。 在列表中右击byte[]类型,在弹出菜单中依次选择List Objects > with incoming references,可以看到,在之前视图中我们点击的类型对应的所有浅堆中的byte array对象都被列了出来。 选择其中一个比较大的对象,点击箭头查看下拉列表中的内容,这里我们可以看到这个对象的整条引用链,其中可以找到我们的bitmap cache。
MAT不能明确的告诉我们哪里发生了泄漏,因为它并不知道这些对象是否还在使用——仅仅只有程序员知道这一点。在这个例子中,缓存占用了应用程序大量的内存,因此在这种情况下,我们应该考虑对cache的大小进行限制。
使用MAT对heap dump进行比较
有时比较两个不同时间点的heap dump对我们分析内存泄漏问题是很有帮助的。首先,我们需要生成两个heap dump文件(别忘记使用 hprof-conv进行转换)
接下来我们可以使用MAT进行比较了(过程稍稍有些复杂)
- 打开第一个HPROF 文件(使用 File > Open Heap Dump)
- 打开Histogram view
- 在Navigation History view中(如果找不到使用 Window > Navigation History打开), 对其中的histogram条目右击,然后选择Add to Compare Basket.
- 重复2~3步骤,打开第二个HPROF文件
- 切换到Compare Basket 页,点击Compare the Results按钮(这页右上角的红色按钮)
在本文中,我展示了如何通过Allocation Tracker 和 heap dumps 这两个工具来更好的认识应用内存的使用情况有。我也展示了如何使用MAT来追踪应用中的内存泄漏问题。MAT是个强大的工具,而我在这里仅仅只是提到了它的皮毛。如果你想要了解更多相关内容,我建议你阅读下面的文章
- Memory Analyzer News: Eclipse MAT官方blog
- Markus Kohler 的java性能分析博客有很多有用的文章, 包括 Analysing the Memory Usage of Android Applications with the Eclipse Memory Analyzer 和10 Useful Tips for the Eclipse Memory Analyzer.