英文原文地址:http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html
The Dalvik runtime may be garbage-collected, but that doesn't mean you can ignore memory management. You should be especially mindful of memory usage on mobile devices, where memory is more constrained. In this article, we're going to take a look at some of the memory profiling tools in the Android SDK that can help you trim your application's memory usage.
Dalvik虚拟机支持垃圾收集,但是这不意味着你可以不用关心内存管理。你应该格外注意移动设备的内存使用,在上面内存空间是受到限制的。在这篇文章里面,我们来看看Android SDK里面的一些内存剖析工具(profiling tools)是如何帮助我们修整应用程序的内存使用。
Some memory usage problems are obvious. For example, if your app leaks memory every time the user touches the screen, it will probably trigger an OutOfMemoryError
eventually and crash your app. Other problems are more subtle, and may just degrade the performance of both your app (as garbage collections are more frequent and take longer) and the entire system.
一些内存使用问题是很明显的,例如,如果在每次用户触摸屏幕的时候应用程序有内存泄露,将会有可能触发OutOfMemoryError,最终程序崩溃。另外一些问题却很微妙,也许只是降低应用程序和整个系统的性能(当高频率和长时间地运行垃圾收集器的时候)。
Tools of the trade
必要的工具:
The Android SDK provides two main ways of profiling the memory usage of an app: the Allocation Trackertab in DDMS, and heap dumps. The Allocation Tracker is useful when you want to get a sense of what kinds of allocation are happening over a given time period, but it doesn't give you any information about the overall state of your application's heap. For more information about the Allocation Tracker, see the article on Tracking Memory Allocations. The rest of this article will focus on heap dumps, which are a more powerful memory analysis tool.
Android SDK提供了2个主要的剖析应用程序内存使用情况的工具:DDMS里的一个分页Allocation Tracker和heap dumps。Allocation Tracker是很有用的,特别是当你想得到程序在一定的时间里内存的分配情况的一种感性认识的时候。但是它不能给你任何关于程序heap总体情况的任何信息。关于Allocation Tracker的更多信息,请看文章Tracking Memory Allocations。文章剩下的内容将把重点放在heap dumps,它是更强大的内存分析工具。
A heap dump is a snapshot of an application's heap, which is stored in a binary format called HPROF. Dalvik uses a format that is similar, but not identical, to the HPROF tool in Java. There are a few ways to generate a heap dump of a running Android app. One way is to use the Dump HPROF file button in DDMS. If you need to be more precise about when the dump is created, you can also create a heap dump programmatically by using the android.os.Debug.dumpHprofData()
function.
一个heap dump就是一个程序heap的快照,它保存为一种叫做HPROF的二进制格式。Dalvik用的也是类似的格式,但是不完全一样。这里是Java的HPROF工具。有很多方法去生成一个运行时应用程序的heap dump。其中一种就是使用在DDMS里边的Dump HPROF file按钮。如果想产生更精确的dump数据,可以在程序中使用android.os.Debug.dumpHprofData()方法。
To analyze a heap dump, you can use a standard tool like jhat or the Eclipse Memory Analyzer (MAT). However, first you'll need to convert the .hprof file from the Dalvik format to the J2SE HPROF format. You can do this using the hprof-conv
tool provided in the Android SDK. For example:
分析heap dump,你可以使用一些标准的工具比如jhat或者Eclipse Memory Analyzer(MAT)。不过,首先你需要把.hprof文件从Dalvik格式转换成J2SE HPROF格式。你可以使用Android SDK提供的hprof-conv工具。例如:
<code><span class="pln" style="color: rgb(0, 0, 0);">hprof</span><span class="pun" style="color: rgb(102, 102, 0);">-</span><span class="pln" style="color: rgb(0, 0, 0);">conv </span><span class="kwd" style="color: rgb(0, 0, 136);">dump</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">hprof converted</span><span class="pun" style="color: rgb(102, 102, 0);">-</span><span class="kwd" style="color: rgb(0, 0, 136);">dump</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">hprof</span></code>
Example: Debugging a memory leak
调试一个内存泄露实例:
In the Dalvik runtime, the programmer doesn't explicitly allocate and free memory, so you can't really leak memory like you can in languages like C and C++. A "memory leak" in your code is when you keep a reference to an object that is no longer needed. Sometimes a single reference can prevent a large set of objects from being garbage collected.
在Dalvik运行时里边,程序员不能显式地分配和释放内存,所以这里的内存泄露跟c和c++里面的不同。在你的代码里边,内存泄露就是你保留了一个并不再需要的类对象的引用。有时候仅仅一个引用就会阻碍gc对一大堆对象的回收。
Let's walk through an example using the Honeycomb Gallery sample app from the Android SDK. It's a simple photo gallery application that demonstrates how to use some of the new Honeycomb APIs. (To build and download the sample code, see the instructions.) We're going to deliberately add a memory leak to this app in order to demonstrate how it could be debugged.
我们来过一个实际的例子,Android SDK里面提供的范例程序Honeycomb Gallery sample app。它是一个photo gallery程序,用来演示一些新的Honeycomb API的使用。(下载和编译这些代码,请看这些命令。)我们会有意地加入一个内存泄露在程序里边,然后来演示如何调试它。
Imagine that we want to modify this app to pull images from the network. In order to make it more responsive, we might decide to implement a cache which holds recently-viewed images. We can do that by making a few small changes to ContentFragment.java. At the top of the class, let's add a new static variable:
想象一下我们想修改程序让它从网络下载图片。为了让它更具备灵活性,我们可以考虑实现一个缓存,保存最近查看过的图片。我们可以对ContentFragment.java做一些小的修改来达到这个目的。在class顶部,我们增加一个新的静态变量:
<code><span class="kwd" style="color: rgb(0, 0, 136);">private</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="kwd" style="color: rgb(0, 0, 136);">static</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">HashMap</span><span class="pun" style="color: rgb(102, 102, 0);"><</span><span class="typ" style="color: rgb(102, 0, 102);">String</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="typ" style="color: rgb(102, 0, 102);">Bitmap</span><span class="pun" style="color: rgb(102, 102, 0);">></span><span class="pln" style="color: rgb(0, 0, 0);"> sBitmapCache </span><span class="pun" style="color: rgb(102, 102, 0);">=</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="kwd" style="color: rgb(0, 0, 136);">new</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">HashMap</span><span class="pun" style="color: rgb(102, 102, 0);"><</span><span class="typ" style="color: rgb(102, 0, 102);">String</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="typ" style="color: rgb(102, 0, 102);">Bitmap</span><span class="pun" style="color: rgb(102, 102, 0);">>();</span></code>
This is where we'll cache the Bitmaps that we load. Now we can change the
updateContentAndRecycleBitmap()
method to check the cache before loading, and to add Bitmaps to the cache after they're loaded.这里就是我们保存缓存的地方。现在我们可以修改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);
- }
I've deliberately introduced a memory leak here: we add Bitmaps to the cache without ever removing them. In a real app, we'd probably want to limit the size of the cache in some way.
我已经在这里故意引入了一个内存泄露的问题:我们把图片加入了缓存但是从来没有移除他们。在真实的应用里,我们可以会用某种方法来限制缓存的大小。
Examining heap usage in DDMS
在DDMS里检查heap的使用情况
The Dalvik Debug Monitor Server (DDMS) is one of the primary Android debugging tools. DDMS is part of the ADT Eclipse plug-in, and a standalone version can also be found in the tools/
directory of the Android SDK. For more information on DDMS, see Using DDMS.
Dalvik Debug Monitor Server(DDMS)是主要的Android调试工具之一,也是ADT Eclipse plug-in的一部分,独立的程序版本也可以在Android SDK的根目录下的tools/下面找到。关于DDMS更多的信息,请参考使用DDMS。
Let's use DDMS to examine the heap usage of this app. You can start up DDMS in one of two ways:
我们来使用DDMS检查这个应用的heap使用情况。你可以使用下面的两种方法启动DDMS:
- from Eclipse: click Window > Open Perspective > Other... > DDMS
- or from the command line: run
ddms
(or./ddms
on Mac/Linux) in thetools/
directory
Select the process com.example.android.hcgallery
in the left pane, and then click the Show heap updates button in the toolbar. Then, switch to the VM Heap tab in DDMS. It shows some basic stats about our heap memory usage, updated after every GC. To see the first update, click the Cause GC button.
在左边的面板选择进程com.example.android.hcgallery,然后在工具条上边点击Show heap updates按钮。这个时候切换到DDMS的VM Heap分页。它会显示每次gc后heap内存的一些基本数据。要看第一次gc后的数据内容,点击Cause GC按钮:
We can see that our live set (the Allocated column) is a little over 8MB. Now flip through the photos, and watch that number go up. Since there are only 13 photos in this app, the amount of memory we leak is bounded. In some ways, this is the worst kind of leak to have, because we never get an OutOfMemoryError
indicating that we are leaking.
我们可以看到现在的值(Allocated列)是有一些超过8MB。现在滑动相片,这时看到数据在增大。因为只有仅仅13个相片在程序里边,所以泄露的内存只有这么大。在某种程度上来说,这时最坏的一种内存泄露,因为我们没法得到OutOfMemoryError来提醒我们说现在内存溢出了。
Creating a heap dump
生成heap dump
Let's use a heap dump to track down the problem. Click the Dump HPROF file button in the DDMS toolbar, choose where you want to save the file, and then run hprof-conv
on it. In this example, I'll be using the standalone version of MAT (version 1.0.1), available from the MAT download site.
我们现在使用heap dump来追踪这个问题。点击DDMS工具条上面的Dump HPROF文件按钮,选择文件存储位置,然后在运行hprof-conv。在这个例子里我们使用独立的MAT版本(版本1.0.1),从MAT站点下载。
If you're running ADT (which includes a plug-in version of DDMS) and have MAT installed in Eclipse as well, clicking the “dump HPROF” button will automatically do the conversion (using hprof-conv) and open the converted hprof file into Eclipse (which will be opened by MAT).
如果你使用ADT(它包含DDMS的插件)同时也在eclipse里面安装了MAT,点击“dump HPROF”按钮将会自动地做转换(用hprof-conv)同时会在eclipse里面打开转换后的hprof文件(它其实用MAT打开)。
Analyzing heap dumps using MAT
用MAT分析heap dumps
Start up MAT and load the converted HPROF file we just created. MAT is a powerful tool, and it's beyond the scope of this article to explain all it's features, so I'm just going to show you one way you can use it to detect a leak: the Histogram view. The Histogram view shows a list of classes sortable by the number of instances, the shallow heap (total amount of memory used by all instances), or the retained heap (total amount of memory kept alive by all instances, including other objects that they have references to).
启动MAT然后加载刚才我们生成的HPROF文件。MAT是一个强大的工具,讲述它所有的特性超出了本文的范围,所以我只想演示一种你可以用来检测泄露的方法:直方图(Histogram)视图。它显示了一个可以排序的类实例的列表,内容包括:shallow heap(所有实例的内存使用总和),或者retained heap(所有类实例被分配的内存总和,里面也包括他们所有引用的对象)。
If we sort by shallow heap, we can see that instances of byte[]
are at the top. As of Android 3.0 (Honeycomb), the pixel data for Bitmap objects is stored in byte arrays (previously it was not stored in the Dalvik heap), and based on the size of these objects, it's a safe bet that they are the backing memory for our leaked bitmaps.
如果我们按照shallow heap排序,我们可以看到byte[]实例在顶端。自从Android3.0(Honeycomb),Bitmap的像素数据被存储在byte数组里(之前是被存储在Dalvik的heap里),所以基于这个对象的大小来判断,不用说它一定是我们泄露掉的bitmap。
Right-click on the byte[]
class and select List Objects > with incoming references. This produces a list of all byte arrays in the heap, which we can sort based on Shallow Heap usage.
右击byte[]类然后选择List Objects > with incoming references。它会生成一个heap上的所有byte数组的列表,在列表里,我们可以按照Shallow Heap的使用情况来排序。
Pick one of the big objects, and drill down on it. This will show you the path from the root set to the object -- the chain of references that keeps this object alive. Lo and behold, there's our bitmap cache!
选择并展开一个比较大的对象,它将展示从根到这个对象的路径--就是一条保证对象有效的链条。注意看,这个就是我们的bitmap缓存!
MAT can't tell us for sure that this is a leak, because it doesn't know whether these objects are needed or not -- only the programmer can do that. In this case, the cache is using a large amount of memory relative to the rest of the application, so we might consider limiting the size of the cache.
MAT不会明确告诉我们这就是泄露,因为它也不知道这个东西是不是程序还需要的,只有程序员知道。在这个案例里面,缓存使用的大量的内存会影响到后面的应用程序,所以我们可以考虑限制缓存的大小。
Comparing heap dumps with MAT
使用MAT比较heap dumps
When debugging memory leaks, sometimes it's useful to compare the heap state at two different points in time. To do this, you'll need to create two separate HPROF files (don't forget to convert them using hprof-conv
).
Here's how you can compare two heap dumps in MAT (it's a little complicated):
调试内存泄露时,有时候适时比较2个地方的heap状态是很有用的。这时你就需要生成2个单独的HPROF文件(不要忘了转换格式)。下面是一些关于如何在MAT里比较2个heap dumps的内容(有一点复杂):
- Open the first HPROF file (using File > Open Heap Dump).
- Open the Histogram view.
- In the Navigation History view (use Window > Navigation History if it's not visible), right click on histogram and selectAdd to Compare Basket.
- Open the second HPROF file and repeat steps 2 and 3.
- Switch to the Compare Basket view, and click Compare the Results (the red "!" icon in the top right corner of the view).
- 第一个HPROF 文件(using File > Open Heap Dump).
- 打开 Histogram view.
- 在Navigation History view里 (如果看不到就从Window > Navigation History找), 右击histogram 然后选择Add to Compare Basket.
- 打开第二个HPROF 文件然后重做步骤2和3.
- 切换到Compare Basket view, 然后点击Compare the Results (视图右上角的红色"!"图标)。
Conclusion
总结
In this article, I've shown how the Allocation Tracker and heap dumps can give you get a better sense of your application's memory usage. I also showed how The Eclipse Memory Analyzer (MAT) can help you track down memory leaks in your app. MAT is a powerful tool, and I've only scratched the surface of what you can do with it. If you'd like to learn more, I recommend reading some of these articles:
- Memory Analyzer News: The official blog of the Eclipse MAT project
- Markus Kohler's Java Performance blog has many helpful articles, including Analysing the Memory Usage of Android Applications with the Eclipse Memory Analyzer and 10 Useful Tips for the Eclipse Memory Analyzer.
这本篇文章里面,我展示了Allocation Tracker和heap dumps是如何给你一种对程序内存使用的感性认识。我也展示了Eclipse Memory Analyzer(MAT)可以帮助追逐我们程序里面的内存泄露问题。MAT是一个强大的工具,我也仅仅触碰了一些皮毛,如果你想学习更多内容,我建议读一些下面的文章:
- Memory Analyzer News: Eclipse MAT project的官方博客。
- Markus Kohler的Java Performance blog有很多有用的文章, 包括 Analysing the Memory Usage of Android Applications with the Eclipse Memory Analyzer and10 Useful Tips for the Eclipse Memory Analyzer.