android APP開發的記憶體管理與優化之一 ——LowMemory Killer
從事長期的android APP開發後,有一個開發者都會注意到的問題——記憶體使用。這裡就總結一下我個人開發過程中對於安卓的記憶體管理機制的一些認識,以及一些優化方案。
基於個人的一些開發習慣,我對安卓應用的記憶體機制分三個部分來講述下:
1. LowMemory Killer 2. dalvik虛擬機器記憶體管理 3. Ashmem和 Pmem首先是LowMemoryKiller(以下簡稱LMK) ,任何APP開發者都應該關注這個機制,它決定了你的應用何時基於什麼自身狀況會被殺死。死生大事,不可不察。
LMK: android應用層的基本管理構架之一。通過權重系統,殺死程序回收管理記憶體空間。
特點:相對於Linux標準OOM(Out Of Memory)機制更加靈活,它可以根據需要殺死程序來釋放需要的記憶體。 原始碼位於drivers/staging/android/lowmemorykiller.c
LMK既然是基於一個權重系統來殺死回收程序的,那麼首要任務自然是確定上下尊卑。那麼這個等級制度是怎麼確定的呢?Android將程序分為6個等級,它們按優先順序順序由高到低依次是:
1.前臺程序(FOREGROUND_APP)
2.可視程序(VISIBLE_APP)
3. 次要服務程序(SECONDARY_SERVER)
4.後臺程序 (HIDDEN_APP)
5.內容供應節點(CONTENT_PROVIDER)
6.空程序(EMPTY_APP)
在android中,程序的oom_adj值也就代表了它的優先順序。oom_adj值越高代表該程序優先順序越低。檔案/init.rc中有以下屬性設定:
setpropro.FOREGROUND_APP_ADJ0
setpropro.VISIBLE_APP_ADJ1
setpropro.SECONDARY_SERVER_ADJ2
setpropro.HIDDEN_APP_MIN_ADJ7
setpropro.CONTENT_PROVIDER_ADJ14
setpropro.EMPTY_APP_ADJ15
/init.rc中,將PID為1的程序(init程序)的oom_adj
# Set init itsforked children's oom_adj.
write /proc/1/oom_adj -16
各個層級的記憶體閥值在LMK程式碼裡都有預設值,不過仍然可以通過init.rc來修改:檔案/init.rc中:
setpropro.FOREGROUND_APP_MEM1536 // 6M
setpropro.VISIBLE_APP_MEM2048 // 8M
setpropro.SECONDARY_SERVER_MEM4096 // 16M
setpropro.HIDDEN_APP_MEM5120 // 20M
setpropro.CONTENT_PROVIDER_MEM5632 // 22.4M
setpropro.EMPTY_APP_MEM6144 // 24M
這些數字也就是對應的記憶體閾值,一旦低於該值,Android便開始按順序關閉相應等級的程序。
注意這些數字的單位是page: 1page = 4 kB。所以上面的六個數字對應的就是(MB):6,8,16,20,22,24。
可以通過命令來檢視當前的記憶體閥值:cat/sys/module/lowmemorykiller/parameters/minfree
也可以重新設定該值(對應不同的需求):echo "1536,2048,4096,5120,15360,23040">/sys/module/lowmemorykiller/parameters/minfree
這樣當可用記憶體低於90MB的時候便開始殺死"空程序",而當可用記憶體低於60MB的時候才開始殺死"內容供應節點"類程序。
這裡閒叨叨一句,init程序的oom_adj設定為-16,從而保證init程序永遠不會被殺掉。
關於LMK的具體實現:
LowMemory Killer是通過註冊CacheShrinker來實現的。CacheShrinker是標準linuxkernel回收記憶體頁面的一種機制,它由核心執行緒kswapd監控,當空閒記憶體頁面不足時,kswapd會呼叫註冊的Shrinker回撥函式,來回收記憶體頁面。
LowMemory Killer是在模組初始化時註冊CacheShrinker的,程式碼如下:
staticint __initlowmem_init(void){
register_shrinker(&lowmem_shrinker); //註冊 CacheShrinker
return0;
}
lowmem_shrinker的定義如下:
staticstructshrinkerlowmem_shrinker = {
.shrink= lowmem_shrink,
.seeks= DEFAULT_SEEKS * 16
};
register_shrinker會將lowmem_shrink加入Shrinker List中,被kswapd在遍歷Shrinker
List時呼叫,而LowMemory Killer
的功能就是在lowmem_shrink中實現的。lowmem_shrink用兩個陣列作為選擇Bad
程序的依據,這兩個陣列的定義如下:
staticintlowmem_adj[6] ={
0,
1,
6,
12,
};
static intlowmem_adj_size = 4;
static size_tlowmem_minfree[6] ={
3*512, // 6MB
2*1024, // 8MB
4*1024, // 16MB
16*1024, // 64MB
};
lowmem_shrink首先計算當前空閒記憶體的大小,如果小於某個閾值,則以該閾值對應的優先順序為基準,遍歷各個程序,計算每個程序佔用記憶體的大小,找出優先順序大於基準優先順序的程序,在這些程序中選擇優先順序最大的殺死,如果優先順序相同,則選擇佔用記憶體最多的程序。
lowmem_shrink殺死程序的方法是向程序傳送一個不可以忽略或阻塞的SIGKILL訊號:
force_sig(SIGKILL,selected);
關於瞭解LMK之後的記憶體管理建議:
1. 我個人是長期在手機廠商從事APP和framework開發的。從外部來看,如果一個應用的記憶體總是不夠用而又經常被回收,那麼是可以從各個層面來緩解這種情況的——設定應用程序的oom_adj為更低(這種修改不僅可在底層也可在app層進行),修改各等級閥值等等,這是在不涉及應用本身修改的情況下,能做出的有限修改。可惜對於日常的應用開發者來說,這絕對不是康莊大道。
2.對於一般的應用開發者而言,安卓裝置的底層已經封閉,甚至ROOT許可權也無法獲得,他們能做的更多是從應用自身進行優化,來使得應用更不容易被殺死回收。要做到這一點,就要深刻理解LMK的權重系統。比如執行那些需要後臺執行的任務,用service而不要用後臺程序;又比如如果你在開發輸入法等應用,你自然能通過某些設定使得它變成高優先順序的可視程序而不是後臺程序。
3.當然,既然有所謂閥值,最治本的方法當然是減少程序的記憶體佔用。作為普通應用開發者,你不能預設自己的應用能始終在前臺,更不可能無限制地重啟自己的程序(最好別這麼幹),提高應用各層級的閥值,鍥而不捨地減少應用各個部分所佔用的記憶體,並更順暢地幫助記憶體的釋放(是的。。。JAVA),才是開發記憶體佔用小,執行穩定順暢的好應用的優秀開發者素質。