Android应用的内存分析

Android内存调试
本文介绍如何使用Android SDK中的工具,如DDMS和MAT,来检测和分析应用中的内存使用情况及内存泄露问题。
原文连接: 点击打开链接,译文如下:

        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工具来实现该转换,示例:

  1. hprof-conv <span style="color:#3333FF;">dump</span>.hprof converted-<span style="color:#3333FF;">dump</span>.hprof  

实例:调试内存泄露

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

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


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

  1. private static HashMap<String,Bitmap> sBitmapCache = new HashMap<String,Bitmap>();  
这就是要加载的位图的缓存。修改 updateContentAndRecycleBitmap()方法,使其在加载前先检测缓存,加载后将图片添加到缓存。
  1. void updateContentAndRecycleBitmap(int category, int position) {  
  2.     if (mCurrentActionMode != null) {  
  3.         mCurrentActionMode.finish();  
  4.     }  
  5.    
  6.     // Get the bitmap that needs to be drawn and update the ImageView.  
  7.    
  8.     // Check if the Bitmap is already in the cache  
  9.     String bitmapId = "" + category + "." + position;  
  10.     mBitmap = sBitmapCache.get(bitmapId);  
  11.    
  12.     if (mBitmap == null) {  
  13.         // It's not in the cache, so load the Bitmap and add it to the cache.  
  14.         // DANGER! We add items to this cache without ever removing any.  
  15.         mBitmap = Directory.getCategory(category).getEntry(position)  
  16.                 .getBitmap(getResources());  
  17.         sBitmapCache.put(bitmapId, mBitmap);  
  18.     }  
  19.     ((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap);  
  20. }  
在这里,我们有意引入了内存泄露:只添加图片到缓存但从不移除。实际中,我们可能会通过一些手段来限制缓存的大小。

利用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 Analyzer10 Useful Tips for the Eclipse Memory Analyzer
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值