Android应用内存泄露分析
原文地址http://developer.android.com/tools/debugging/debugging-memory.html
因为Android是为移动设备设计的,所以我们应该一直注意应用使用了多少内存。尽管Dalvik虚拟机会进行常规的垃圾回收,这并不意味这可以忽略 应用内存的分配和释放。为了提供一个稳定的用户体验,使app之间迅速的进行切换,当用户不与应用交互时应该减少不必要的内存消耗。
尽管在开发时遵守了管理内存的最佳方法,但我们仍可能内存泄露或者引起其他内存的问题。确定应用使用了尽可能少内存的方法是使用工具分析应用的内存,下面展示了几种分析方法。
- 解读Log信息
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)的增长,如果这个值不断在增长,没有变小,说明存在内存泄露。
- 查看堆更新
1.打开DeviceMonitor sdk/tools/monitor
2.在 DebugMonitor window,选择应用的进程
3.点击进程列表上面的UpdateHeap按钮
4.在面板的右边选择Heap页
Heap视图显示了堆内存的基本使用情况,会在每一次的垃圾回收后进行更新。点击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命令即可查看:
</blockquote>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 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
通常,你应该关心的只用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。