您的位置:首页 > 移动开发 > Android开发

Android应用内存泄露分析

2015-03-18 23:29 211 查看
原文地址http://developer.android.com/tools/debugging/debugging-memory.html

因为Android是为移动设备设计的,所以我们应该一直注意应用使用了多少内存。尽管Dalvik虚拟机会进行常规的垃圾回收,这并不意味这可以忽略应用内存的分配和释放。为了提供一个稳定的用户体验,使app之间迅速的进行切换,当用户不与应用交互时应该减少不必要的内存消耗。

尽管在开发时遵守了管理内存的最佳方法,但我们仍可能内存泄露或者引起其他内存的问题。确定应用使用了尽可能少内存的方法是使用工具分析应用的内存,下面展示了几种分析方法。

解读Log信息

Dalvik的log信息是最简单的分析应用内存的地方,每一次垃圾回收,logcat会打印以下格式的信息:

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

GC原因

垃圾手机被触发的原因和类型有以下几种:

GC_CONCURRENT

当堆内存将要满的时候进行的并发的垃圾回收以释放内存

GC_FOR_MALLOC

当堆内存已经满时,app尝试分配内存会引发的垃圾回收,这时候系统会停止应用执行来回收内存

GC_HPROF_DUMP_HEAP

当创建一个HRPOF文件来分析内存时引起的垃圾回收

GC_EXPLICIT

明确的垃圾回收,也就是调用gc()(应该避免调用这个方法,相信垃圾回收器在需要的时候会执行)方法会执行的垃圾回收

GC_EXTERNAL_ALLOC

这个只存在于API 10或者小于10(新版本任何对象都在Dalvik堆中进行分配),对外部分配内存的垃圾回收(比如存在于native内存的像素数据)

释放量

此次垃圾回收释放的内存量

堆信息统计

未被占用内存的百分比和(存活对象占内存的大小/堆总共的大小)

外部内存统计

在API10及其以下分配的外部内存,(分配的内存大小/限制值,当超过这个限制时会进行回收)

暂停时间

更大的堆会有更长时间的暂停。并发的暂停时间显示两个暂停:一个是在回收开始的时候,另一个是在接近结束时。

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

不断观察log中的这些信息,注意堆状态信息(上面的3571K/9991K)的增长,如果这个值不断在增长,没有变小,说明存在内存泄露。

查看堆更新

为了获取应用使用内存的种类类和分配时间,你可以通过Device Monitor来查看应用堆的即时更新:

1.打开DeviceMonitor sdk/tools/monitor

2.在 DebugMonitor window,选择应用的进程

3.点击进程列表上面的UpdateHeap按钮

4.在面板的右边选择Heap页

Heap视图显示了堆内存的基本使用情况,会在每一次的垃圾回收后进行更新。点击Cause GC按钮来看第一次的更新



图 DeviceMonitor工具,1为Update Heap,2为Cause GC按钮

不断与应用进行交互,在垃圾回收后来查看堆内存分配情况,这可以帮助你找出哪些操作可能引起太多的内存分配,在哪些地方来尝试进行减少分配和释放资源。

跟踪分配

当你开始缩小内存问题的范围后,你应该使用Allocation Tracker来确认问题对象是在哪里分配的。AllocationTracker不仅对寻找内存的具体使用有帮助,而且对分析关键代码的执行路径很有帮助

比如跟踪应用list滑动时的内存分配,你可以查看那个操作所有的内存分配,在哪个线程,在哪里分配。这是非常有用的,通过对调用路径的分析,来减少不必要的工作,提高UI的流畅度。

使用方法:

1.打开DeviceMonitor

2.在DDMS窗口,选择要观察的进程

3.在右边的面板上,选择AllocationTracker页

4.点击StartTracking.

5.与app进行交互,以便你想要分析的代码执行

5.当你想要更新分配列表时,点击Get Allocations按钮



这个列表显示了所有最近的分配对象,目前被512条数据的环形缓冲区限制。选择一行来查看导致对象分配的方法调用栈,栈不仅显示了分配对象的类型,还显示了在哪个线程,哪个类,哪个文件的哪一行,非常强大。
尽管不可能移除所有UI关键代码路径的对象分配,allocation tracker 可以帮助你分析你代码的问题所在。举个例子,一些应用可能在draw方法每一次执行的时候都创建一个新的paint,把这个对象改为global可以帮助提升性能。

查看内存的整体分配

进一步的分析,你可能想知道应用的内存都被哪些不同类型的内存分配占用了,通过下面的adb命令即可查看:

adb shell dumpsys meminfo<package_name>

这个命令会列出应用目前的内存分配情况,单位是KB

当分析这些信息时,你应该熟悉下面几种类型的分配:

Private (Clean and Dirty) RAM

这些内存是只被你的进程使用的。这是当你的应用被销毁后系统可以回收的内存量。通常,最重要的列是“private dirty”,它的消耗是非常昂贵的,因为只能被你的进程使用,并且它的内容只能存在于内存并且不能被交换到外部存储中(因为Android没有使用swap)。所有本进程的Dalvik和native堆的分配都是privatedirty的内存,与Zygote进程共享的Dalvik和native分配是shared dirty的内存。

Proportional Set Size (PSS)

这是应用的内存使用大小,包含了进程间共享的页。任何只属于你进程的内存页直接全部算在PSS值中,与其它进程共享的内存页,按照成比例的量算进PSS值中,比如,两个进程共享一个页,那么每一个进程的PSS都只增加该页大小的一半。

PSS测量一个好的特性是可以将所有进程的PSS加起来确定所有进程的内存使用量,这意味这PSS是衡量进程内存占用,进程间内存使用对比和所有有效内存的好方法。

例如,下面是Gmail进程在平板设备上的内存使用情况,里面有很多信息,但关键点是下面列的几个:

** MEMINFO in pid 9953 [com.google.android.gm] **
  Pss  Pss  Shared Private  Shared Private Heap Heap Heap
TotalCleanDirtyDirtyCleanClean SizeAlloc Free
  ------  ------  ------  ------  ------  ------  ------  ------  ------
  Native Heap0 0 0 0 0 0 7800 7637(6)  126
  Dalvik Heap5110(3) 0 4136 4988(3) 0 0 9168 8958(6)  210
 Dalvik Other2850 0 2684 2772 0 0
  Stack  36 0 836 0 0
 Cursor 136 0 0  136 0 0
 Ashmem  12 028 0 0 0
 Other dev 380 024  376 0 4
  .so mmap5443(5) 1996 2584 2664(5) 5788 1996(5)
 .apk mmap 23532 0 0 125232
 .ttf mmap  3612 0 08812
 .dex mmap3019(5) 2148 0 0 8936 2148(5)
Other mmap 107 0 8 8  32468
Unknown6994(4) 0  252 6992(4) 0 0
  TOTAL  24358(1) 4188 972417972(2)16388 4260(2)1696816595  336

 Objects
Views: 426ViewRootImpl:  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


通常,你应该关心的只用PssTotal和Private Dirty两列。在某些情况下,PrivateClean和Heap Alloc列也提供了有趣的数据。下面是不同内存分配种类的信息:

Dalvik Heap

应用中Dalvik使用的内存。PssTotal包含所有Zygote进程中的分配(在多个进程中共享根据权重来算),PrivateDirty是你的应用单独使用的堆内存,由应用自己的内存分配和从Zygote进程复制后来后又进行修改过的内存页。

在比较新的版本里有DalvikOther项,Pss Total和Private Dirty显示的Dalvik Heap数据,并不包含Dalvik的开销,比如JIT和GC的开销,在旧版本上面都把它们归并到了Dalvik里。

Heap Alloc

Heap Alloc是Dalvik和native堆分配器跟踪应用app消耗的内存,这个值比Pss Total和Private Dirty大,因为你的进程是从Zygote进程复制的,它包含了与其他进程共享分配的内存。



.
so mmap 和.dex mmap

被用来映射.so和.dex代码的内存,Pss Total包含了应用之间共享的平台性代码,Private Clean是应用自身代码消耗的内存,通常,实际的映射内存会更加大,这里显示的内存只是需要被app执行的代码所占用的大小,但是,.so映射会占用很大的private dirty内存,因为当代码被加载到最后的地址时需要转换为本地代码。

Unknown

任何系统不能分类到其他种类的内存页,目前,它包含了大部分native分配,因为ASLR技术的存在,不能被工具识别。就像Dalvik一样,Pss Total包含了与Zygote共享的部分,Private Dirty是只属于你的app的未知内存。

Total

你的进程使用的Pss内存总共的大小,这是所有PSS列数据之和,它表明了你的进程所占用内存的大小,可以与其他进程和有效内存进行对比。

Private Dirty和Private Clean是你进程的总共分配内存,它是不与其它进程共享的。它们加起来(特别是Private Dirty)是当进程被销毁后系统可以回收的内存。Dirty内存是那些被修改的必须保留提交到内存的页,clean内存是那些映射持久化文件到内存所占用的页(比如被执行的代码),如果一段时间不被使用可以被交换出去。



ViewRootImpl

你的进程中活跃的rootview的数量,每一个root view都和一个window关联,所有这可以帮助你区分涉及到dialog或者其他widows的内存泄露

AppContext和Activities

你的进程中存在的Context和Activity对象的数量,这对快速识别由于静态引用导致Activity对象的泄露是很有帮助的,这些对象引用了其他许多的对象,所以如果泄露会导致其他大量的内存不能被释放。一个View或者Drawable会保持一个对Activity的引用,所有保持一个View或者Drawable不被释放也会导致你的app泄露一个Activity。

通过MAT工具进行分析,后面会有文章专门介绍
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: