官方視頻翻譯,如需轉載,請註明做者: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 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 殺掉的是用戶正在交互或能夠感知的進程,將會致使很是不友好的用戶體驗。因此 Android SystemServer 進程維護了一張進程優先級列表,LMK 根據這張表來決定先殺死哪一個進程:
如上圖所示,當已用內存超過 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 的用法,畢竟這是他們團隊開發的。因此,我強力推薦大家也參加一下明天的宣講會。
上面提到,TOTAL 是 PSS,那麼這張圖中,除了 Java Heap,其它的是什麼意思呢?對於這部份內存佔用,咱們又能作什麼呢?
這就比較好玩了,由於這部分大可能是由 Android 平臺產生的,若是你真的想理解他們,那麼你須要學習不少專業知識。好比 Framework 是如何實現 View 系統及 Resource 管理的,Native Code 是如何執行的,WebView 是如何工做的,Android Runtime 是如何執行你的代碼的,HAL 如何管理你的 Graphics 以及 Linux 內核的虛擬內存管理方式等等。
順便說一下,我生活在這兒,這個橘黃色的方塊裏(Android Runtime):
那麼,對於平臺產生的內存佔用,咱們須要使用工具來診斷嗎?首先,咱們可使用:
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)
複製代碼
下面又介紹了幾種工具,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。
內存優化,任重而道遠。