Android应用的内存分析

来源:互联网 发布:江西科技学院网络选课 编辑:程序博客网 时间:2024/05/16 11:07

        原文连接:点击打开链接,译文如下:

        Dalvik虚拟机会进行垃圾回收,但这并不意味可以忽视内存管理,反而更应该留意内存受限的移动设备上的内存使用情况。这篇文章中,我们一起去看看Android SDK中的几个内存分析工具,这些工具可以帮你跟踪应用中的内存使用情况。

        有些内存使用问题比较明显,例如:如果应用在用户每次触摸屏幕时都发生内存泄露,可能最终将触发OutOfMemoryError错误,并使应用崩溃。而有些问题则比较微妙,可能只是降低了应用(由于频繁的垃圾回收占用了较长时间)或整个系统的性能。

商业化工具

        Android SDK提供了两种主要的应用程序内存使用情况分析途径:DDMS中的Allocation Tracker标签和堆转(heap dump)。Allocation Tracker用来获取给定时间里发生的各种内存分配情况,但无法给出应用程序堆的整体信息。关于Allocation Tracker的更多信息可以看文章Tracking Memory Allocations(译文连接:点击打开链接)。本文其余内容重点阐述堆转,一种更为强大的内存分析工具。

        堆转是应用程序内存使用情况的快照,以名为HPROF的二进制格式存储。Dalvik虚拟机使用的格式与java HPROF工具中的相似但不完全相同。有多种方式能为正在运行的应用程序生成堆转,方式之一就是利用DDMS的Dump HPROF file按钮。如果要了解更为详细的堆转信息,可以使用android.os.Debug.dumpHprofData() 函数创建堆栈获取程序。

        分析堆转,可以使用jhat或Eclipse的MAT等标准工具。但是,我们首先需要将Dalvik产生的.hprof文件转换为J2SE支持的HPROF格式,可以用Android SDK中的hprof-conv工具来实现该转换,示例:

hprof-conv dump.hprof converted-dump.hprof

实例:调试内存泄露

        Dalvik镜像中,程序员不需要显示的分配和释放内存,因此,不会像在C和C++中那样(由于内存的分配和释放没有处理好)发生内存泄露,只会在持续引用一个不再需要的对象时发生内存泄露。有时候一个单引用会使得一大批对象难以被垃圾回收(博主注:前者翻译的防止内存泄露一文中对Activity Context的引用就属此例)。

        让我们以Android SDK中的HoneycombGallery(博主注:示例位于$SDK/samples/android-11/HoneycombGallery)为例,该例只是一个简单的照片画廊应用,展示了如何使用Honeycomb新增的部分API。(要编译和下载示例代码,可以看instructions)。这里我们故意在该应用中加入内存泄露代码,以便于演示如何对其进行内存泄露调试。


        假设我们要修改程序,实现从网络获取显示图片。为了使其反应更灵敏,我们可能会实现一个保存最近浏览过的图片的缓存,通过对ContentFragment.java做很小的改动就可以实现。在该class文件的顶部添加一个静态变量:

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);}
在这里,我们有意引入了内存泄露:只添加图片到缓存但从不移除。实际中,我们可能会通过一些手段来限制缓存的大小。

利用DDMS来检测堆的使用情况

        DDMS(Dalvik Debug Monitor Server)是Android主要调试工具之一,也是Eclipse的ADT(Android Debug Tools)插件的一部分。在Android SDK的tools/目录下可以发现它的独立版本。更多关于DDMS的信息,请看Using DDMS。

        让我们用DDMS来检测上述应用堆的使用情况,可以使用下面两种方式的其中之一来启动DDMS:

1)、通过Eclipse: 单击Window > Open Perspective > Other... > DDMS

2)、通过命令行:在tools/目录下运行 ddms ( Mac/Linux上执行./ddms)


在Eclipse左边的窗口中选择进程com.example.android.hcgallery,并单击工具栏上的Show heap updates按钮;然后,在Eclipse右侧的窗口中切换到DDMS的VM Heap标签(博主注:不同版本的中的标签和按钮名称可能有所不同,我的Eclipse版本是Helios,工具栏上的按钮名为Update Heap,DDMS中的标签名为Heap,但无论名称是什么,功能和位置都是一样的)。每次垃圾回收后都会更新VM Heap中显示的堆内存使用情况的一些基本统计信息。如果要看首次更新的情况,单击Cause GC按钮。


        可以看出程序的分配(Allocated栏)空间略微超过了8MB。现在翻动图片,并观察到该值变大了。由于这里应用中只有13张图片,内存泄露的大小是有限的。从某中意思上来说,这是一种最坏的内存泄露方式,因为,决不会有OutOfMemoryError向我们指示有内存泄露。

创建一个堆转

         让我们用堆转来追踪问题。单击DDMS工具栏上的 Dump HPROF file按钮,选择保存路径,然后,执行hprof-conv命令。本例中,将使用独立版本的MAT(version 1.0.1)进行堆转分析,从MAT download site下载。

         如果运行了ADT(DDMS的插件版),同时,MAT也已经在Eclipse中安装了,当单击Dump HPROF file按钮时,会自动完成转换(使用hprof-conv),并在Eclipse中打开转换后的hprof文件(使用MAT打开)。

使用MAT分析堆转

        启动MAT并装载转换后的HPROF文件。MAT是一个功能强大的工具,但其特性的介绍超出了本文的范围,所以这里只演示一种侦测内存泄露的方法:直方图。直方图显示的是class列表,这些class是按实例化数进行排序的。shallow heap(所有实例使用的堆的大小总和),retained heap(所有实例使用的仍处于活动状态的堆的大小总和,包括引用的其他对象)


        如果按照shallow heap的排序,可以看出byte[]的实例高居首位。由于Android 3.0(Honeycomb)中,位图的像素数据存储在byte数组中(之前版本没有将其放入Dalvik的堆中),基于这些对象的大小,下了一个安全赌注,对于我们泄露的位图而言,将byte数组视为备用内存。

        右单击byte[ ]类,选择 List Objects > with incoming references,生成一个堆中所有byte数组的列表,我们可以基于shallow heap的使用情况对其进行排序。

        选择byte[ ]中大对象的其中之一,并一路追踪下去,就会看到从根到对象的整条路径——一条存放活动对象的引用链。瞧,以下就是我们的位图缓存:


        MAT无法确切告诉我们这是泄露,因为它不知道这些对象是否还需要——只有程序员能做到。这种情况下,相对其他应用该缓存使用了大量的堆,因此,我们可能会考虑限制下缓存的大小。

用MAT进行堆转比较

        调试内存泄露时,有时候比较两个不同时间点的堆转状态是有用的。为此,需要创建两个独立的HPROF文件(别忘了使用hprof-conv对它们进行转换)。

        以下是如何用MAT比较两个堆转(有点复杂):

1)、打开第一个HPROF文件(使用File > Open Heap Dump

2)、打开直方图

3)、在Navigation History窗口中(如果不可见,用Window > Navigation History打开),右击histogram并选择Add to Compare Basket

4)、打开第二个HPROF文件,重复2)、3)两步

5)、切换到Compare Basket窗口,单击Compare the Results(位于窗口右上角的红色“!”图标)

总结

        本文中,我们向你展示了Allocation Tracker和堆转如何给你在分析应用内存泄露时带来更好的感觉。同时还展示了如何用Eclipse的MAT工具追踪应用中的内存泄露。MAT是个功能强大的工具,这里只是介绍了它的一些皮毛。如果你想了解更多,推荐你阅读以下一些文章:

1)、Memory Analyzer News:Eclipse MAT工程的官方博客

2)、Markus Kohler的Java性能研究博客:有很多有用的文章,包括:Analysing the Memory Usage of Android Applications with the Eclipse Memory Analyzer和10 Useful Tips for the Eclipse Memory Analyzer。