Android應用內存泄露分析

jopen 9年前發布 | 45K 次閱讀 Android Android開發 移動開發

   原文地址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
               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。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!