Android 内存分析总结

一直没有写博客的习惯,最近觉得年纪貌似有点大了,不像以前记忆这么好,想找个方式梳理一下知识,刚好最近在ITeye上看之前一起工作过的一个大哥写的一些关于状态机的一些东西,就萌生了也写一写,记录一下的想法,第一篇就先说一说Android内存方面的一些事情吧。

很多人在进行Android开发的过程中经常会碰到内存方面的问题,比如说内存溢出,应用莫名奇妙的Crash,有时也会被客户抱怨说在第三方的应用管理软件中内存开销非常的大,这时候就需要做一些内存分析和优化的事情,本文主要来讨论内存分析的事情。

       很多人一看到OOM就觉得是内存有泄漏,其实OOM并不总是由于内存泄漏所致,我个人认为,所谓泄漏是指内存无法被收回,就是说有一段逻辑会导致一些内存无法被收回,如果反复执行这段逻辑,内存会持续的增长,最后导致OOM,很多时候,OOM是我们不恰当的使用内存(尤其是图片的处理)所致,比如,在较短的时间加载了很多规格较大的图片,这方面的问题,本文也不做深入讨论(其实Google Guide有很深入的讨论,可参考   Displaying Bitmaps Efficiently 一文),我们重点来说说如何分析内存。

要分析内存,我们首先要找到衡量内存大小的一个标准,就是到底使用了多少内存,很不幸,由于Linux的内存共享机制,每个应用使用了多少内存却是笔糊涂帐,通常我们有下面几种方式来衡量:

  • VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
  • RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
  • PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
  • USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS,一般我们会用PSS来作为内存大小的衡量值,我曾经饭编译过一些内存管理软件,显示使用的内存其实就是PSS

我们可以通过命令

adb shell procrank来查看内存状况

   PID      Vss      Rss      Pss      Uss  cmdline
  494   57372K   57232K   33128K   30336K  system_server
  585   46924K   46708K   25082K   23464K  com.android.systemui

通过

adb shell top来查看内存(VSS RSS)和CPU使用状况:

PID PR CPU% S  #THR     VSS     RSS PCY UID      Name
 2735  0   0% R     1   1212K    504K     shell    top
  950  0   0% S    18 661416K  25736K  fg u0_a19   android.process.media

adb shell dumpsys meminfo <package_name>

下面这个是guide里面gmail的一个dump,能够比较清晰全面的看到当前内存的使用状况,具体的含义guide里面有详细的解释,这里想说的是Dalvik Heap 和我们下面要说到的Heap moniter 中的heap size和GC 日志中 GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, 中的heap status是一样的(实际测试也是一样的),

通常我们说一个应用最多只能分配多少多少的内存实际上指的也是Dalvik Heap(在system/build.prop中定义),当应用分配的内存要超过这个定义时,就会报OOM,这个也就解释了说为什么我们通过第三方应用开我们的应用已经使用了50,甚至 60M的内存,我们的heap size只有32M,但却没有OOM,因为第三方显示的是PSS,是包含了共享内存的(这也是科学的),通过这个我们可以清楚的看到我们的内存状况是怎样的,这样内存优化也会比较有针对性。

                 Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap
               Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free
              ------  ------  ------  ------  ------  ------  ------  ------  ------
  Native Heap      0       0       0       0       0       0    7800    7637(6)  126
  Dalvik Heap   5110(3)    0    4136    4988(3)    0       0    9168    8958(6)  210
 Dalvik Other   2850       0    2684    2772       0       0
        Stack     36       0       8      36       0       0
       Cursor    136       0       0     136       0       0
       Ashmem     12       0      28       0       0       0
    Other dev    380       0      24     376       0       4
     .so mmap   5443(5) 1996    2584    2664(5) 5788    1996(5)
    .apk mmap    235      32       0       0    1252      32
    .ttf mmap     36      12       0       0      88      12
    .dex mmap   3019(5) 2148       0       0    8936    2148(5)
   Other mmap    107       0       8       8     324      68
      Unknown   6994(4)    0     252    6992(4)    0       0
        TOTAL  24358(1) 4188    9724   17972(2)16388    4260(2)16968   16595     336
 
 Objects
               Views:    426         ViewRootImpl:        3(8)
         AppContexts:      6(7)        Activities:        2(7)
              Assets:      2        AssetManagers:        2
       Local Binders:     64        Proxy Binders:       34
    Death Recipients:      0
     OpenSSL Sockets:      1
 
 SQL
         MEMORY_USED:   1739
  PAGECACHE_OVERFLOW:   1164          MALLOC_SIZE:       62

通过DDMS 自带的Heap moniter也是不错的选择:

另外,有一个非常著名的Eclipse插件,叫MAT(Memory Analyzer Tool),非常的好用,尤其是它的Memory leak suspects,当你的内存一直持续的增长,增长到很大的一个值时,用MAT的Memory leak suspects,几乎 100%会指出你leak的地方在哪:



Allocation Tracker:

这个工具可以让我们在一个时间段跟踪内存的分配状况,在DDMS的Allocation Tracker:点击Start Tracking,点击几下你的应用后,再点击Get Allocations 就可以看到:

图片右下方的 at XXX 一大堆的就是一层层的调用关系,可以清晰的看到到底是谁申请了内存,申请了多少。

我们甚至还可以在代码中将将瞬态内存打印出来:


public static void printMeminfo(String where)
{
ActivityManager mgr = (ActivityManager) mContext
.getSystemService(Context.ACTIVITY_SERVICE);
int id = android.os.Process.myPid();
int[] ids = { id };
MemoryInfo[] minfo = mgr.getProcessMemoryInfo(ids);
Log.d("meminfo", where + " dalvikPrivateDirty:"
+ minfo[0].dalvikPrivateDirty + " dalvikPss:"
+ minfo[0].dalvikPss + " TotalPss: " + minfo[0].getTotalPss()
+ " TotalPrivateDirty: " + minfo[0].getTotalPrivateDirty());
}

我们在日志中经常看到有内存回收的打印:

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

其含义为:

D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>

其中GC Reason 有如下几个,我看到网上的一些文章,对GC Reason说的并不正确,这里我把guide中的原文一并贴上:

GC_CONCURRENT
这个应该的当我们的Heap size 快要被填满的时候触发的一个并发的内存回收 (A concurrent garbage collection that frees up memory as your heap begins to fill up.)
,通常
GC_CONCURRENT能回收较多的内存( Heap size都快满了,用了不少的内存,应该有一部分不需要了),
下面是截取的部分此类型的回收日志:
写道
12-12 08:01:09.230: D/dalvikvm(1972): GC_CONCURRENT freed 3360K, 20% free 15422K/19052K, paused 3ms+4ms, total 46ms
12-12 08:01:10.420: D/dalvikvm(585): GC_CONCURRENT freed 11144K, 52% free 10641K/21936K, paused 3ms+4ms, total 38ms
12-12 08:01:10.890: D/dalvikvm(1336): GC_CONCURRENT freed 266K, 7% free 6321K/6736K, paused 8ms+3ms, total 36ms
12-12 08:01:10.910: D/dalvikvm(585): GC_CONCURRENT freed 1451K, 51% free 10881K/21936K, paused 3ms+3ms, total 34ms
12-12 08:01:11.000: D/dalvikvm(585): GC_CONCURRENT freed 2127K, 52% free 10616K/21936K, paused 2ms+3ms, total 22ms
12-12 08:01:11.050: D/dalvikvm(585): GC_CONCURRENT freed 1431K, 51% free 10883K/21936K, paused 2ms+2ms, total 20ms
12-12 08:01:12.900: D/dalvikvm(843): GC_FOR_ALLOC freed 3323K, 22% free 14150K/18120K, paused 25ms, total 25ms
12-12 08:01:13.630: D/dalvikvm(680): GC_CONCURRENT freed 156K, 6% free 5658K/5960K, paused 1ms+4ms, total 26ms
12-12 08:01:13.720: D/dalvikvm(680): GC_CONCURRENT freed 356K, 8% free 5812K/6312K, paused 3ms+2ms, total 19ms
 
GC_FOR_MALLOC
这个是说我们的应用尝试去分配内存而这时候和heap已经快满了(不够用了),这个时候系统会把我们的应用停下来然后进行内存回收,通常 GC_FOR_MALLOC 我们的heap size会增大
(A garbage collection caused because your app attempted to allocate memory when your heap was already full, so the system had to stop your app and reclaim memory.)
12-12 07:04:10.780: D/dalvikvm(1424): GC_FOR_ALLOC freed 27K, 6% free 7332K/7768K, paused 17ms, total 17ms
12-12 07:04:10.810: D/dalvikvm(1424): GC_CONCURRENT freed <1K, 6% free 7332K/7768K, paused 3ms+1ms, total 21ms
12-12 07:04:10.850: D/dalvikvm(1424): GC_FOR_ALLOC freed <1K, 6% free 7332K/7768K, paused 22ms, total 22ms
12-12 07:04:10.870: D/dalvikvm(1424): GC_FOR_ALLOC freed 0K, 4% free 10655K/11092K, paused 19ms, total 19ms
 
12-12 07:04:11.950: D/dalvikvm(1972): GC_CONCURRENT freed 3295K, 19% free 15445K/18896K, paused 4ms+5ms, total 56ms
12-12 07:04:13.060: D/dalvikvm(1972): GC_CONCURRENT freed 3480K, 20% free 15365K/19004K, paused 2ms+3ms, total 54ms
12-12 07:04:15.470: D/dalvikvm(1972): GC_FOR_ALLOC freed 1763K, 20% free 15336K/19004K, paused 44ms, total 44ms
12-12 07:04:16.120: D/dalvikvm(1972): GC_FOR_ALLOC freed 3K, 20% free 15392K/19064K, paused 25ms, total 25ms
12-12 07:04:17.740: D/dalvikvm(585): GC_FOR_ALLOC freed 3557K, 20% free 17650K/21936K, paused 28ms, total 29ms
 
      GC_HPROF_DUMP_HEAP
     这个就不解释了,我们在做内存分析创建HPROF(MAT可以分析该文件)的时候会打印
(A garbage collection that occurs when you create an HPROF file to analyze your heap.)
GC_EXPLICIT
这个是主动调用系统gc方法触发的GC(在DDMS 点击GC就可以看到)
An explicit garbage collection, such as when you call gc() (which you should avoid calling and instead trust the garbage collector to run when needed).
GC_EXTERNAL_ALLOC
这个看原文就好,主要是回收非Dalvik heap中的数据的(以前Android的图片数据是存在native层的 。。。。)
This happens only on API level 10 and lower (newer versions allocate everything in the Dalvik heap). A garbage collection for externally allocated memory (such as the pixel data stored in native memory or NIO byte buffers).

猜你喜欢

转载自kingbo203.iteye.com/blog/1988636