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命令即可查看:
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。