官方視訊翻譯,如需轉載,請註明作者:Yuloran (t.cn/EGU6c76)
演講人介紹
Rechard Uhler,Android Runtime 開發工程師。為便於寫作,筆者將以第一人稱視角對視訊內容進行概述。
前言
想要進行記憶體優化,就必須對 Android 記憶體管理機制有比較深入的瞭解,這樣才能保證應用在低端機上也能有良好的表現。不同的記憶體型別,包括 Shared Memory,Dex Memory 以及 GPU Memory, 都會對使用者體驗產生影響。
我在過去的三年時間裡,都在致力於深入理解 Android 應用記憶體管理機制。那麼,為什麼 App 開發工程師也要關注記憶體佔用呢?於我而言,主要是因為 Android 生態系統。如果一個 Android 應用在低端裝置上使用者體驗不好(比如經常卡頓),那麼 OEM(Original Entrusted Manufacture) 就不願再生產這樣的裝置,進而導致這部分使用者被排除在 Android 生態系統之外。
本次課題主要討論三點內容:
-
低記憶體時 Android 系統的工作機制
-
如何評估應用記憶體使用情況
-
如何減少應用記憶體佔用
低記憶體時 Android 系統的工作機制
首先,需要介紹實體記憶體的概念,然後引入 Android Low Memory Killer。
實體記憶體
裝置的實體記憶體被分為很多頁(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。
記憶體優化,任重而道遠。