ANDROID 探究oom内幕

从早期G1的192MB RAM开始,到如今动辄1G -2G RAM的设备,为单个App分配的内存从16MB到48MB甚至更多,但OOM从未曾离咱们远去。这是由于大部分App中图片内容占据了50%甚至75%以上,而App内容的极大丰富,所需的图片愈来愈多,屏幕尺寸也愈来愈大分辨率也愈来愈高,所需的图片的大小也跟着往上涨,这在大屏手机和平板上尤为明显。并且还常常要兼容低版本的设备。因此Android的内存管理显得极为重要。android

在这里咱们主要讲两件事情:
1.Gingerbread和Honeycomb中的一些影响你使用内存的变化
-heap size
-GC
-bitmaps
2.理解heap的用途分配
-logs
-merory leaks
-Eclispe Memory Analyzer(MAT)数组

首先第一部分,咱们都知道Android是个多任务操做系统,同时运行着不少程序,都须要分配内存,不可能为一个程序分配愈来愈多的内存以致于让整个系统都崩溃,所以heap的大小有个硬性的限制,跟设备相关,从发展来讲也是愈来愈大,G1:16MB,Droid:24MB,Nexus One:32MB,Xoom:48MB,可是一旦超出了这个使用的范围,OOM便产生了。若是你正在开发一个应用,想知道设备的heap大小的限制是多少,比方说根据这个值来估算本身应用的缓存大小应该限制在什么样一个水平,你可使用ActivityManager.getMemoryClass ()来得到一个单位为MB的整数值,通常来讲最低很多于16MB,对于如今的设备而言这个值会愈来愈大,24MB,32MB,48MB甚至更大。浏览器

可是对于一些内存很是吃紧的好比图片浏览器等应用,在平板上所需的内存更大了。所以在Honeycomb以后AndroidManifest.xml增长了largeHeap的选项
<application
       android:largeHeap="true"
       ...
</application>
这容许你的应用使用更多的heap,能够用ActivityManager.getLargeMemoryClass ()返回一个更大的可用heap size。可是这里要警告的是,千万不要由于你的应用报OOM了而使用这个选项,由于更大的heap size意味着更多的GC时间,意味着应用的性能愈来愈差,并且用户也会发现其余应用颇有可能会内存不足。只有你须要使用不少的内存并且很是了解每一部份内存的用途,这些所需的内存都是不可或缺的,这个时候你才应该使用这个选项。缓存

刚刚咱们提到更大的heap size意味着更多的GC时间,下面咱们来谈谈Garbage Collection。并发

 

如上图所示,GC会选择一些它了解还存活的对象做为内存遍历的根节点,比方说thread stack中的变量,JNI中的全局变量,zygote中的对象等,而后开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是须要回收的垃圾,会被GC回收掉。以下图蓝色部分。app

2.jpg

 

所以也能够看出,更大的heap size须要遍历的对象更多,回收垃圾的时间更长,因此说使用largeHeap选项会致使更多的GC时间。eclipse

在Gingerbread以前,GC执行的时候整个应用会暂停下来执行全面的垃圾回收,所以有时候会看到应用卡顿的时间比较长,通常来讲>100ms,对用户而言已经足以察觉出来。Gingerbread及以上的版本,GC作了很大的改进,基本上能够说是并发的执行,也不是执行彻底的回收,只有在GC开始以及结束的时候会有很是短暂的停顿时间,通常来讲<5ms,用户也不会察觉到。ide

在Honeycomb以前,Bitmap的内存分配以下图。工具

3.jpg

蓝色部分是Dalvik heap,黄色部分是Bitmap引用对象的堆内存,而Bitmap实际的Pixel Data是分配在Native Memory中。这样作有几个问题,首先须要调用reclyce()来代表Bitmap的Pixel Data占用的内存可回收,不调用这个方法的话就要靠finalizer来让GC回收这部份内存,但了解finalizer的应该都知道这至关的不可靠;其次是很难进行Debug,由于一些内存分析工具是查看不到Native Memory的;再次就是不调用reclyce()须要回收Native Memory中的内存的话会致使一次完整的GC,GC执行的时候会暂停整个应用。性能

Honeycomb以后,Bitmap的内存分配作出了改变,以下图

4.jpg

蓝色黄色部分没有变化,但Bitmap实际的Pixel Data的内存也一样分配在了Dalvik heap中。这样作有几个好处。首先能同步的被GC回收掉;其次Debug变得容易了,由于内存分析工具可以查看到这部分的内存;再次就是GC变成并发了,可作部分的回收,也就是极大缩短了GC执行时暂停的时间。

接下来咱们讲第二部分。通常来讲咱们但愿了解咱们应用内存分配,最基本的就是查看Log信息。比方说看这样一个Log信息(这是Gingerbread版本的,Honeycomb之后log信息有改动):

D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/
9991K, external 4703K/5261K, paused 2ms 2ms

GC_XXX代表是哪类GC以及触发GC的缘由。几种GC类型:
- GC_CONCURRENT:这是由于你的heap内存占用开始往上涨了,为了不heap内存满了而触发执行的。
- GC_FOR_MALLOC:这是因为concurrent gc没有及时执行完而你的应用又须要分配更多的内存,内存要满了,这个时候不得不停下来进行malloc gc。
- GC_EXTERNAL_ALLOC:这是为external分配的内存执行的GC,也就是上文提到的Bitmap Pixel Data之类的。
- GC_HPROF_DUMP_HEAP:这是当你作HPROF这样一个操做去建立一个HPROF profile的时候执行的。
- GC_EXPLICIT:这是因为你显式的调用了System.gc(),这是不提倡的,通常来讲咱们能够信任系统的GC。

freed 2049K代表在此次GC中回收了多少内存。
65% free 3571K/9991K是heap的一些统计数据,代表此次回收后65%的heap可用,存活的对象大小3571K,heap大小是9991K。
external 4703K/5261K是Native Memory的数据。放Bitmap Pixel Data或者是NIO Direct Buffer之类的。第一个数字代表Native Memory中已分配了多少内存,第二个值有点相似一个浮动的阀值,代表分配内存达到这个值系统就会触发一次GC进行内存回收。
paused 2ms 2ms代表GC暂停的时间。从这里你能够看到越大的heap size你须要暂停的时间越长。若是是concurrent gc你会看到2个时间一个开始一个结束,这时间是很短的,但若是是其余类型的GC,你极可能只会看到一个时间,而这个时间是相对比较长的。

经过Log能够对内存信息有个基本的了解,但这不足以了解什么对象在使用内存,在哪使用了内存。这时候你须要用Heap Dumps。一个Heap Dump基本上来讲就是一个包含你heap中全部对象信息的二进制文件。你能够用DDMS来生成这个文件,点击DDMS中下图的那个按钮。

5.jpg

同时Heap Dumps也有对应的API,你想要在特定的时间点获取一份Heap Dump,使用android.os.Debug.dumpHprofData()。获取到的文件要转换成标准的HPROF格式,使用以下命令:hprof-conv orig.hprof converted.hprof。而后用MAT或者jhat进行分析。

在讲MAT以前先讲下Memory Leaks。要清楚GC并不能防止Memory Leaks,所谓Memory Leaks就是引用到了已经没用的对象从而让这些对象避免了被GC回收,跟C/C++中的概念并不同。容易致使内存泄漏的是一些Activity,Context,View,Drawable之类的引用,和一些非静态的内部类比方说Runnable之类的以及一些Caches。好比你旋转屏幕的时候在新的方向上产生一个新的Activity,若是有变量引用到旧的Activity就会致使其没法被GC,形成Memory Leaks。

一般经过上面介绍的Log信息,只要已用memory一直处于上升的情形而不回落,便大体能了解到应用存在Memory Leaks。不过MAT这类工具能够帮助你更好的对memory进行分析。使用MAT以前有2个概念是要掌握的:Shallow heap和Retained heap。Shallow heap表示对象自己所占内存大小,一个内存大小100bytes的对象Shallow heap就是100bytes。Retained heap表示经过回收这一个对象总共能回收的内存,比方说一个100bytes的对象还直接或者间接地持有了另外3个100bytes的对象引用,回收这个对象的时候若是另外3个对象没有其余引用也能被回收掉的时候,Retained heap就是400bytes。

MAT使用Dominator Tree这样一种来自图形理论的概念。

6.jpg

所谓Dominator,就是Flow Graph中从源节点出发到某个节点的的必经节点。那么根据这个概念咱们能够从上图左侧的Flow Graph构造出右侧的Dominator Tree。这样一来很容易就看出每一个节点的Retained heap了。Shallow heap和Retained heap在MAT中是很是有用的概念,用于内存泄漏的分析。

咱们用Honeycomb3.0中的HoneycombGallery作一个Demo。在工程的MainActivity当中加入以下代码:

 
public class MainActivity extends Activity implements ActionBar.TabListener {
    static Leaky leak = null;
    class Leaky {
        void doSomething() {
            System.out.println("Wheee!!!");
        }
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (leak == null) {
            leak = new Leaky();
        }
        ...

上面这段代码,对Java熟悉的同窗都应该了解内部类对象持有了外部类对象引用,而leak做为静态变量在非空判断下只产生了一个对象,所以当旋转屏幕时生成新的Activity的时候旧的Activity的引用依然被持有,以下图:

8.jpg

经过观察旋转屏幕先后Log中GC的信息也能看出heap的分配往上涨了许多,而且在GC执行完heap的分配稳定以后并无降下来,这就是内存泄漏的迹象

咱们经过MAT来进行分析。先下载MAT,能够做为Eclipse插件下载,也能够做为RCP应用下载,本质上没有区别。DDMS中选中应用对应的进程名,点击Dump HPROF file的按钮,等一小段时间生成HPROF文件,若是是Eclipse插件的话,Eclipse会为这个HPROF自动转化成标准的HPROF并自动打开MAT分析界面。若是是做为RCP应用的话,须要用sdk目录tools中的hprof-conv工具来进行转化,也就是上文说起的命令hprof-conv orig.hprof converted.hprof,这种方式保存HPROF文件的位置选择更为自主,你也能够修改Eclipse的设置让Eclipse提示保存而不是自动打开,在Preferences -> Android -> DDMS中的HPROF Action由Open in Eclipse改成Save to disk。打开MAT,选择转化好的HPROF文件,能够看到Overview的界面以下图:

7.jpg

中间的饼状图就是根据咱们上文所说的Retained heap的概念获得的内存中一些Retained Size最大的对象。点击饼状图能看到这些对象类型,但对内存泄漏的分析还远远不够。再看下方Action中有Dominator TreeHistogram的选项,这通常来讲是最有用的工具。还记得咱们上文说过的Dominator Tree的概念吗,这就是咱们用来跟踪内存泄漏的方式。点开Dominator Tree,会看到以Retained heap排序的一系列对象,以下图:

9.png

Resources类型对象因为通常是系统用于加载资源的,因此Retained heap较大是个比较正常的状况。但咱们注意到下面的Bitmap类型对象的Retained heap也很大,颇有多是因为内存泄漏形成的。因此咱们右键点击这行,选择Path To GC Roots ->exclude weak references,能够看到下图的情形:

10.png

Bitmap最终被leak引用到,这应该是一种不正常的现象,内存泄漏极可能就在这里了。MAT不会告诉哪里是内存泄漏,须要你自行分析,因为这是Demo,是咱们特地形成的内存泄漏,所以比较容易就能看出来,真实的应用场景可能须要你仔细的进行分析。

根据咱们上文介绍的Dominator的概念,leak对象是该Bitmap对象的Dominator,应该出如今Dominator Tree视图里面,但实际上却没有。这是因为MAT并无对weak references作区别对待,这也是咱们选择exclude weak references的缘由。若是咱们Path To GC Roots ->with all references,咱们能够看到下图的情形:

11.png

能够看到还有另一个对象在引用着这个Bitmap对象,了解weak references的同窗应该知道GC是如何处理weak references,所以在内存泄漏分析的时候咱们能够把weak references排除掉。

有些同窗可能但愿根据某种类型的对象个数来分析内存泄漏。咱们在Overview视图中选择Actions -> Histogram,能够看到相似下图的情形:

12.png

上图展现了内存中各类类型的对象个数和Shallow heap,咱们看到byte[]占用Shallow heap最多,那是由于Honeycomb以后Bitmap Pixel Data的内存分配在Dalvik heap中。右键选中byte[]数组,选择List Objects -> with incoming references,能够看到byte[]具体的对象列表:

13.png
14.png

咱们发现第二个byte[]的Retained heap较大,内存泄漏的可能性较大,所以右键选中这行,Path To GC Roots -> exclude weak references,一样能够看到上文所提到的状况,咱们的Bitmap对象被leak所引用到,这里存在着内存泄漏。

15.png

在Histogram视图中第一行<Regex>中输入com.example.android.hcgallery,过滤出咱们本身应用中的类型,以下图:

16.png

咱们发现本应该只有一个MainActivity如今却有两个,显然不正常。右键选择List Objects -> with incoming references,能够看到这两个具体的MainActivity对象。右键选中Retained heap较大的MainActivity,Path To GC Roots -> exclude weak references,再一次可疑对象又指向了leak对象。

17.png
18.png
19.png

以上是MAT一些基本的用法,若是你感兴趣,能够自行深刻的去了解MAT的其余功能。

转载:

http://blog.csdn.net/chengyingzhilian/article/details/8662849

相关文章
相关标签/搜索