原文地址:http://android-developers.blogspot.tw/2011/03/memory-analysis-for-android.html
Dalvik虚拟机运行时可以自动回收垃圾,但是开发者并不能因此忽略内存管理。在内存受限的移动设备上开发者尤其需要重视内存使用。本文将介绍一些android sdk中的内存分析工具,使用这些工具可以帮助开发者优化应用的内存使用。
有一些内存使用的问题很明显,比如,当用户每次触摸屏幕的时候应用都会泄漏内存(leak memory),那么最终很可能会出现OutOfMemoryError导致应用崩溃。其它问题可能很微妙,可能只会降低应用和整个系统的运行性能,垃圾回收会更频繁更耗时。
Tools of the trade
android sdk为分析应用的内存使用主要提供了两种方式:DDMS中的内存分配跟踪器(Allocation Tracker)以及堆信息(heap dump)。如果开发者想知道在一段时间内有哪些内存被分配,那么Allocation Tracker会非常有用,但是它无法提供应用的全局堆信息。关于Allocation Tracker的详细信息可以参见这里。本文的重点将放在heap dump上面,它是一种更强大的内存分析工具。
heap dump文件是应用的堆信息镜像(snapshot),它保存在hprof二进制格式的文件中。Dalvik虚拟机使用一种类似的格式,但是与Java中的hprof工具并不相同。有几种方式可以生成正在运行的android应用的heap dump文件。一种方式是使用DDMS中的Dump HPROF file按钮。如果你想让生成dump文件的时间更完美,你可以使用android.os.Debug.dumpHprofData()方法来编码实现生成heap dump文件。
要分析heap dump文件,开发者可以使用像jhat或者Eclipse Memory Analyzer (MAT)这样的标准工具。然而,开发者必须先将.hprof文件从dalvik格式转换为J2SE HPROF格式。使用android sdk中的hprof-conv工具可以实现,例如:
hprof-conv dump.hprof converted-dump.hprof
实例:调试内存泄漏
在dalvik虚拟机中,开发者并不用显式地分配(allocate)和(free)释放内存,所以不会像c和c++语言中明显的内存泄漏。“内存泄漏”指的是对不再继续使用的对象依然保持引用,有时候一个引用可以导致大量的对量无法被垃圾回收。
我们可以以android sdk中的Honeycomb Gallery sample app作为具体例子。这是一个简单的图片浏览应用,用来示范如何使用有些新的Honeycomb api。接下来我们会故意在该应用中加入内存泄漏以便演示如何调试。
假设我们想修改该应用实现从网络中下载图片。为了让应用更流畅,我们会实现一个缓存对象(cache)用来保存最近浏览过的图片。通过对ContentFragment.java作少许改动即可,我们为它添加一个静态变量:
private static HashMap<String,Bitmap> sBitmapCache = new HashMap<String,Bitmap>();
它用来缓存加载过的Bitmap。接下来我们修改updateContentAndRecycleBitmap()方法,在加载图片之前先检查缓存,在图片加载后将Bitmap添加到缓存。
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);
}
这里我们故意泄漏了内存:我们添加Bitmap到缓存中却从不删除。在实际应用中,我们需要想办法限制缓存的大小。
在DDMS中检测内存使用情况
Dalvik Debug Monitor Server(DDMS) 是主要的android 调试工具之一,它是ADT Eclipse 插件的一部分,也可以在android sdk的 tools/目录下找到它。关于DDMS,更详细的信息可以参见Using DDMS。
接下来我们开始使用DDMS来检查该应用的内存使用情况。开发者可以用下面其中一种方法启动DDMS:
- 从Eclipse:点击Window > Open Perspective > Other... > DDMS
- 从命令行:在tools/目录下执行ddms(Mac/Linux中./ddms)
选择左边面板中的进程com.example.android.hcgallery,然后点击工具栏中的"Show heap updates"按钮。然后切换到DDMS中的VM Heap标签页,这里会显示我们应用内存使用的一些基本数据,在每次GC后更新。要首次看到更新,需要点击"Cause GC"按钮。
我们可以看到live set (Allocated列)略大于8M。在应用中切换图片,可以看到数字上升。因为该应用中只有13张图片,所以泄漏的内存是有界限的。有时候,这是最糟糕的一种泄漏,因为我们无法看到OutOfMemoryError,从而意识到内存泄漏。
生成heap dump文件
接下来我们使用heap dump文件来追踪问题。点击DDMS工具栏中的Dump HPROF file按钮,选择你想保存文件的位置,然后运行hprof-conv。在这个实例中,我使用的是单独的MAT(版本1.0.1),可以从这里下载。
如果你正在运行ADT,并且Ecplise中安装有MAT,点击"dump HPROF"按钮将会自动完成格式转换(使用hprof-conv)并且在Eclipse中打开转换后的hprof文件(用MAT打开)。
用MAT分析heap dump文件
打开MAT并且加载已经转换后的HPROF文件。MAT是一个很强大的工具,介绍它的所有功能超出了这边文章的范畴,在这里我只介绍一种可以检测内存泄漏的方式:Histogram视图。Histogram视图会列出所有的分组(class),可以按照实例数量、shallow heap(所有实例占用的总内存)、retained heap(所有实例持有的总内存,包括它们引用的对象)。
如果我们按照shallow heap排序,可以看到byte[]的实例在最上面。因为在android 3.0中,Bitmap对象的像素值是用byte数组来保存(之前它们并不保存在dalvik heap中),看一下这些对象的数量,我们可以确定这就是我们泄漏了的bitmap的内存。
右击byte[]组并且选择List Objects > with incoming references。这样内存中的所有byte数组都会列出,我们可以通过shallow heap来排序。
选取一个较大的对象并且展开它,可以看到从root set到对象的完整路径--对象的每一级引用。我们的bitmap缓存就在里面。
MAT无法确定地告诉我们这是内存泄漏,因为它也不知到这些对象是否需要--只有开发者自己知道这些。在这种情况下,缓存占用了大量的内存,所以我们应该考虑限制缓存的大小。
使用MAT比较heap dump文件
在调试内存泄漏时,有时比较两个不同时间点的heap dump文件非常有用。首先你得生成两个hprof文件(不要忘记用hprof-conv工具转换)。
下面是如何在MAT中比较两个heap dump文件:
- 打开第一个hprof文件(File > Open Heap Dump)
- 打开Histogram视图
- 在Navigation History视图(如何不可见则点击Windw > Navigation History)中,在histogram上右击并选择Add to Compare Basket
- 打开第二个hprof文件并且重复步骤2和3
- 转到Compare Basket视图,点击Compare the Results(视图右上角的红色"!"图标)
总结
在这边文章中,我展示了如何使用Allocation Tracker和heap dump文件来查看应用的内存使用情况,也展示了如何使用MAT来追踪应用中的内存泄漏。MAT是一个强大的工具,我只是讲了一点皮毛,如果你向学习更多,建议阅读相关的文章:
-
Memory Analyzer News: MAT项目的官方博客
- Markus Kohler的java性能优化博客有很多有用的文章,包括 Analysing the Memory Usage of Android Applications with the Eclipse Memory Analyzer a以及10 Useful Tips for the Eclipse Memory Analyzer.