分析並優化 Android 應用內存佔用

官方視頻翻譯,如需轉載,請註明做者:Yuloran (t.cn/EGU6c76)java

演講人介紹

Rechard Uhler,Android Runtime 開發工程師。爲便於寫做,筆者將以第一人稱視角對視頻內容進行概述。android

視頻地址web

前言

想要進行內存優化,就必須對 Android 內存管理機制有比較深刻的瞭解,這樣才能保證應用在低端機上也能有良好的表現。不一樣的內存類型,包括 Shared Memory,Dex Memory 以及 GPU Memory, 都會對用戶體驗產生影響。shell

我在過去的三年時間裏,都在致力於深刻理解 Android 應用內存管理機制。那麼,爲何 App 開發工程師也要關注內存佔用呢?於我而言,主要是由於 Android 生態系統。若是一個 Android 應用在低端設備上用戶體驗很差(好比常常卡頓),那麼 OEM(Original Entrusted Manufacture) 就不肯再生產這樣的設備,進而致使這部分用戶被排除在 Android 生態系統以外。編程

本次課題主要討論三點內容:緩存

  • 低內存時 Android 系統的工做機制bash

  • 如何評估應用內存使用狀況架構

  • 如何減小應用內存佔用app

低內存時 Android 系統的工做機制

首先,須要介紹物理內存的概念,而後引入 Android Low Memory Killer。ide

物理內存

設備的物理內存被分爲不少頁(Page),每頁 4KB。不一樣的頁用來作不一樣的事情:

橘黃色的是已使用頁,黃色的是緩存頁(數據在磁盤上有備份,因此 Cache Pages 是能夠被回收的),綠色的是空閒頁。

用於回收 Cached pages 的 kswapd 進程:

這是一個 2G 內存的手機,X 軸表示使用時間,Y 軸表示內存使用狀況。隨着打開的應用愈來愈多,Used Pages 也愈來愈多,而 Cached Pages 和 Free Pages 則愈來愈少。當 Free Pages 低於 kswapd 的閾值時,Linux 內核就會經過 kswapd 進程對 Cached Pages 進行回收。當應用再次訪問 Cached Pages 上的內容時,就須要從磁盤上從新加載。若是 Cached Pages 太少的話,設備就可能死機:

因此,在 Android 上咱們有個機制叫 Low Memory Killer,當 Cached Pages 太少時,就會被觸發。它的工做方式是根據進程的優先級,選擇性地殺死某個進程,釋放該進程佔用的全部資源以知足內存分配須要:

如上圖所示,當 Cached Pages 低於 LMK 閾值時,將會觸發低內存殺死機制。

LMK(Low Memory Killer)

若是 LMK 殺掉的是用戶正在交互或能夠感知的進程,將會致使很是不友好的用戶體驗。因此 Android SystemServer 進程維護了一張進程優先級列表,LMK 根據這張表來決定先殺死哪一個進程:

  • Perceptible 指的是非用戶直接交互的進程,好比在後臺播放音樂的音樂播放器進程;
  • Previous 指的是切換至當前前臺應用前的應用進程;
  • Cached 指緩存的進程,這多是退至後臺的應用進程,也多是已經退出的應用進程,目的是爲了實現應用間的快速切換。因此,Cached 進程也是優先級最低的進程:

如上圖所示,當已用內存超過 LMK 閾值時,LMK 將從 Cached 列表底部開始殺死進程。若是可用內存仍是不知足分配須要,那麼將會按照上表所示優先級自底向上殺死進程,直到準備 Kill SystemServer 進程,這將致使手機重啓。

因此,你能夠想象 LMK 在低內存手機上的情景:

如上圖所示,LMK 將一直處於活躍狀態,具體表現就是應用卡頓、桌面黑屏重啓,手機死機等等。如此,OEM 將不肯生產這些設備。

評估應用內存使用狀況

那麼,咱們怎麼知道 App 使用了多少內存呢?

物理內存追蹤

以前提到,設備的物理內存被分爲不少頁(Page),Linux Kernel 將會持續跟蹤每一個進程使用的 Pages,因此只要對進程使用的 Pages 進行計數便可:

但實際狀況遠比這要複雜的多,由於有些 Pages 是進程間共享的:

共享內存頁計數方法:

(1)RSS(Resident Set Size):App 徹底負責:

(2)PSS(Proportional Set Size):App 按比例負責,好比下圖所示兩個進程共享,那就負責一半。若是三個進程共享,那就負責三分之一:

(3)USS(Unique Set Size):App 無責:

但實際上,至少須要系統級別的上下文才能知道識別 RSS 與 USS。因此一般都是使用 PSS 來計算,這也能夠避免多計或者少計 Shared Pages。你可使用:

adb shell dumpsys meminfo -s [process] 
複製代碼

命令來查看一個進程的 PSS 使用狀況:

最底部的 TOTAL 表明的就是應用按比例佔用的總內存大小。

應用內存佔用分析

若是想要應用支持的功能越多,UI 越炫酷,那就須要更多的內存分配。既想馬兒跑,又想馬兒不吃草的事情是不存在的:

內存佔用影響因素:

(1)應用使用場景:很好理解,哪一個頁面比較炫、動效多、或者使用了 webview,那這個時候 App 佔用的內存就高:

(2)平臺配置:很好理解,好比手機的分辨率越高,相同 dp 的圖片佔用的內存就越大,因此高檔手機上,App 的內存佔用確定比低檔手機高:

(3)設備內存壓力:設備內存越緊張,越可能觸發 GC,致使 App 佔用內存比設備內存充裕時低:

因此,你應當在相同的內存壓力下評估你的 App 內存佔用:

因爲內存壓力很差控制,因此建議評估前,先一鍵清理全部進程,而後再測試。

減小應用內存佔用

使用 Android Studio 的 Memory Profiler,能夠查看當前 Java 堆上分配了哪些對象、對象大小以及對象引用鏈和被引用鏈等不少信息。Live Allocation 中有 image heap、zygote heap、app heap 等能夠選擇,可是我建議你只關注 app heap。由於 image heap 和 zygote heap 是 App 啓動時從系統繼承過來的,對於這部份內存佔用,咱們基本上無能爲力:

關於 Memory Profiler 的細節我不會講太多,由於明天中午 12:30 Esteban 將會詳細講解 Profiler 的用法,畢竟這是他們團隊開發的。因此,我強力推薦大家也參加一下明天的宣講會。

Java Heap 之外的內存佔用分析

上面提到,TOTAL 是 PSS,那麼這張圖中,除了 Java Heap,其它的是什麼意思呢?對於這部份內存佔用,咱們又能作什麼呢?

這就比較好玩了,由於這部分大可能是由 Android 平臺產生的,若是你真的想理解他們,那麼你須要學習不少專業知識。好比 Framework 是如何實現 View 系統及 Resource 管理的,Native Code 是如何執行的,WebView 是如何工做的,Android Runtime 是如何執行你的代碼的,HAL 如何管理你的 Graphics 以及 Linux 內核的虛擬內存管理方式等等。

順便說一下,我生活在這兒,這個橘黃色的方塊裏(Android Runtime):

Android 平臺產生的內存佔用診斷

那麼,對於平臺產生的內存佔用,咱們須要使用工具來診斷嗎?首先,咱們可使用:

adb shell dumpsys meminfo -a [process]
複製代碼

來查看更詳細的信息(如下數據爲筆者本身開發的 App 的內存佔用狀況):

Applications Memory Usage (in Kilobytes):
Uptime: 498024399 Realtime: 1230430304

** MEMINFO in pid 10898 [com.yuloran.wanandroid_java] **
                   Pss      Pss   Shared  Private   Shared  Private  SwapPss     Heap     Heap     Heap
                 Total    Clean    Dirty    Dirty    Clean    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------   ------   ------   ------
  Native Heap    35822        0      824    35764       32       24     8740    75776    38786    36989
  Dalvik Heap     4001        0      304     3552       72      412      240     6847     3424     3423
 Dalvik Other     5256        0       48     5256        0        0        0                           
        Stack      120        0        4      120        0        0        0                           
       Ashmem      130        0        4      128        4        0        0                           
      Gfx dev     2596        0        0     2596        0        0        0                           
    Other dev       16        0      104        0        0       16        0                           
     .so mmap    23782    22188     1132      504    13320    22188       15                           
    .jar mmap       68        0        8       68        0        0        0                           
    .apk mmap     8029       24        0     7684     1872       24        0                           
    .ttf mmap      223       20        0        0      956       20        0                           
    .dex mmap    21974    19864        0       20    13080    19864        0                           
    .oat mmap      377       64        0        0     3620       64        0                           
    .art mmap     6547      404      868     5852     7584      404       24                           
   Other mmap      408        0       12        8      644      376        0                           
   EGL mtrack    24660        0        0    24660        0        0        0                           
    GL mtrack     4524        0        0     4524        0        0        0                           
      Unknown     2130        0      184     2124        0        0        0                           
        TOTAL   140702    42564     3492    92860    41184    43392       39    82623    42210    40412
 
 Dalvik Details
        .Heap     3308        0        0     3308        0        0        0                           
         .LOS       42        0       16       12        4       28        4                           
 .LinearAlloc     4020        0       20     4020        0        0        0                           
          .GC      384        0       16      384        0        0        0                           
    .JITCache      596        0        0      596        0        0        0                           
      .Zygote      583        0      288      164       68      384        0                           
   .NonMoving       68        0        0       68        0        0        0                           
 .IndirectRef      256        0       12      256        0        0        0                           
 
 App Summary
                       Pss(KB)
                        ------
           Java Heap:     9808
         Native Heap:    35764
                Code:    50436
               Stack:      120
            Graphics:    31780
       Private Other:     8344
              System:     4450
 
               TOTAL:   140702       TOTAL SWAP PSS:       39
 
 Objects
               Views:      207         ViewRootImpl:        1
         AppContexts:        3           Activities:        1
              Assets:       18        AssetManagers:        3
       Local Binders:       24        Proxy Binders:       23
       Parcel memory:        8         Parcel count:       34
    Death Recipients:        3      OpenSSL Sockets:        0
            WebViews:        0
 
 SQL
         MEMORY_USED:      345
  PAGECACHE_OVERFLOW:       55          MALLOC_SIZE:      117
 
 DATABASES
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         4       20             41        17/38/5  /data/user/0/com.yuloran.wanandroid_java/databases/app_database.db
         4       12                         0/0/0    (attached) temp
         4       20             40         3/19/4  /data/user/0/com.yuloran.wanandroid_java/databases/app_database.db (1)
複製代碼
  • Private Dirty Memory 相似於以前說過的 Used Memory;
  • Private Clean Memory 相似於 以前說過的 Cached Memory。

下面又介紹了幾種工具,showmap、ahat、debug malloc等,略。。。由於他下面說到:

總的來講就是:能夠,但不必。由於這須要瞭解不少專業知識,並且不少數據是可見但不可控的。

內存優化建議

(1)優化 Java 堆上的對象:

不少內存雖然不在 Java 堆分配,可是其生命週期跟 Java 堆上分配的對象相綁定:

因此,優化 Java Heap 上的對象,也有助於其它類型內存的回收。

(2)減少 apk 體積:

由於不少在 apk 中佔據磁盤空間的文件,在運行期也會佔據內存空間:

由於 apk 佔據的磁盤空間大小是固定的,因此壓縮 apk 大小比下降內存佔用更容易。更多 apk 大小優化方法請查看 Best Practices to Slim Down Your App Size

結語

本期視頻主要講述了 Android 的 Low Memory Killer 機制、如何評估應用的內存使用狀況以及如何減小應用內存佔用,來源於 Google Android Runtime 開發工程師 Rechard Uhler 的經驗總結,能夠說很靠譜了。

就筆者自身的開發經驗來看,內存泄露比較容易解決,只是有的泄露是因爲第三方 SDK 或者 Framework 致使的,此時只能經過反射來修復。若是反射也修復不了,可是不存在持續泄露,即僅泄露一次,也能夠不做處理,或者經過商務推進去解決。而減小內存佔用則比較困難,畢竟要想 App 功能豐富,那勢必會佔用更多的內存。並且如今不少項目是多人團隊開發,每一個人可能只負責一小塊,對整個應用的掌控能力不足,進行內存調休就更困難了。因此,內存調優工做須要豐富的編程經驗及架構經驗,除了 Java 之外,還須要對 Android 的不少 UI 控件有比較深刻的理解,由於在 Android 平臺上,內存佔用大頭永遠是 UI,主要是 Bitmap。

內存優化,任重而道遠。

相關文章
相關標籤/搜索