Android 作業系統的記憶體回收機制
Android APP 的運行環境
Android 是一款基於 Linux 核心,面向移動終端的作業系統。為適應其作為移動平台作業系統的特殊需要,Google對其做了特別的設計與最佳化,使得其進程調度與資源管理與其他平台的 Linux 有明顯的區別。主要包含下面幾個層次:
Application Framework
Application Framework 將整個作業系統分隔成兩個部分。對應用開發人員而言,所有 APP 都是運行在 Application Framework 之上,而並不需要關心系統底層的情況。Application Framework 層為應用開發人員提供了豐富的應用編程介面,如 Activity Manager,Content Provider,Notification Manager,以及各種視窗 Widget 資源等。在 Application Framework 層,Activity 是一個 APP 最基本的組成部分。一般每個 Activity 對應於螢幕上的一個視圖(或者說一屏),一個 APP 可以有一個或者多個 Activity。應用程式被打包成 .apk 格式的檔案,由 Dalvik VM 解釋執行。
Dalvik VM
Dalvik 虛擬機器採用寄存器架構,而不是 JVM 的棧結構。Java 程式編譯後的 .class 檔案並不能在 Dalvik 中解釋執行。因此 Google 提供了一個 dx 工具,用於將 .class 檔案轉換成 Dalivk 能夠識別的 .dex 格式。具體 Dalvik VM 的細節不是本文重點,以下不再討論。
Linux kernel
由上所述,所有的 APP 都是由 Java 代碼編寫並在 Dalvik VM 中得到解釋執行。在 Android 作業系統中,每個 Dalvik VM 的每個 Instance 都對應於 Linux 核心中的一個進程。可以使用 adb shell 工具查看系統中的當前進程。如所示,Android2.3.3 啟動後核心中的進程列表。
UID 標識為 app_xx 的每一項都是一個 app 所佔用的進程,可見 Android 設計使得每個應用程式由一個獨立的 Dalvik 執行個體解釋執行,而每個 Linux 核心進程載入一個 Dalvik 執行個體,通過這種方式提供 app 的運行環境。如此,每個 APP 的資源被完全屏蔽,互不干擾。雖然同時引入了處理序間通訊的困難,但也帶來了更強的安全性。
Android 記憶體回收原則
下面將從 Application Framework 和 Linux kernel 兩個層次分析 Android 作業系統的資源管理機制。
Android 之所以採用特殊的資源管理機制,原因在於其設計之初就是面向移動終端,所有可用的記憶體僅限於系統 RAM,必須針對這種限制設計相應的最佳化方案。當 Android 應用程式退出時,並不清理其所佔用的記憶體,Linux 核心進程也相應的繼續存在,所謂“退出但不關閉”。從而使得使用者調用程式時能夠在第一時間得到響應。當系統記憶體不足時,系統將啟用記憶體回收過程。為了不因記憶體回收影響使用者體驗(如殺死當前的活動進程),Android 基於進程中啟動並執行組件及其狀態規定了預設的五個回收優先順序:
IMPORTANCE_FOREGROUND:IMPORTANCE_VISIBLE:IMPORTANCE_SERVICE:IMPORTANCE_BACKGROUND:IMPORTANCE_EMPTY:
這幾種優先順序的回收順序是 Empty process、Background process、Service process、Visible process、Foreground process。關於劃分原則參見 http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html檔案中。
ActivityManagerService 集中管理所有進程的記憶體資源分派。所有進程需要申請或釋放記憶體之前必須調用 ActivityManagerService 對象,獲得其“許可”之後才能進行下一步操作,或者 ActivityManagerService 將直接“代勞”。類 ActivityManagerService 中涉及到記憶體回收的幾個重要的成員方法如下:trimApplications(),updateOomAdjLocked(),activityIdleInternal() 。這幾個成員方法主要負責 Android 預設的記憶體回收機制,若 Linux 核心中的記憶體回收機制沒有被禁用,則跳過預設回收。
預設回收過程
Android 作業系統中的記憶體回收可分為兩個層次,即預設記憶體回收與核心級記憶體回收,本章重點對預設記憶體回收機制進行研究,Linux 核心層次的記憶體回收機制將在下一張介紹。 本章所有代碼可參見 ActivityManagerService.java。
回收動作入口:activityIdleInternal()
Android 系統中記憶體回收的觸發點大致可分為三種情況。第一,使用者程式調用 StartActivity(), 使當前活動的 Activity 被覆蓋;第二,使用者按 back 鍵,退出當前應用程式;第三,啟動一個新的應用程式。這些能夠觸發記憶體回收的事件最終調用的函數介面就是 activityIdleInternal()。當 ActivityManagerService 接收到非同步訊息 IDLE_TIMEOUT_MSG 或者 IDLE_NOW_MSG 時,activityIdleInternal() 將會被調用。代碼如下:
清單 1. IDLE_NOW_MSG 的處理方式
case IDLE_NOW_MSG:{ IBinder token = (Ibinder)msg.obj; activityIdle(token, null); } break;
清單 2. IDLE_TIMEOUT_MSG 的處理方式
case IDLE_TIMEOUT_MSG: { if (mDidDexOpt) { mDidDexOpt = false; Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); nmsg.obj = msg.obj; mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); return; } IBinder token = (IBinder)msg.obj; Slog.w(TAG, "Activity idle timeout for " + token); activityIdleInternal(token, true, null); } break;
IDLE_NOW_MSG 由 Activity 的切換以及 Activiy 焦點的改變等事件引發,IDLE_TIMEOUT_MSG 在 Activity 啟動逾時的情況下引發,一般這個逾時時間設為 10s,如果 10s 之內一個 Activity 依然沒有成功啟動,那麼將發送非同步訊息 IDLE_TIMEOUT_MSG 進行資源回收。activityIdleInternal() 的主要任務是改變系統中 Activity 的狀態資訊,並將其添加到不同狀態列表中。其主要工作如下:
首先,調用 scheduleAppGcsLocked() 方法通知所有進行中的任務進行記憶體回收。scheduleAppGcsLocked() 將進行調度 JVM 的 garbage collect,回收一部分記憶體空間,這裡僅僅是通知每個進程自行進程垃圾檢查並調度回收時間,而非同步回收。然後,取出 mStoppingActivities 和 mFinishigActivities 列表中的所有內容,暫存在臨時變數中。這兩個列表分別儲存了目前狀態為 stop 和 finishi 的 activity 對象。對於 stop 列表,如果其中的 activity 的 finish 狀態為 true,判斷是不是要立即停止,如果要立即停止則調用 destroyActivityLocked() 通知目標進程調用 onDestroy() 方法,否則,先調用 resumeTopActivity() 運行下一個 Activity。如果 finish 狀態為 false,則調用 stopActivityLocked() 通知客戶進程停止該 Activity,這種情況一般發生在調用 startActivity() 後。對於 finish 列表,直接調用 destroyActivityLocked() 通知客戶進程銷毀目標 Activity。
這裡的 destroyActivityLocked 等函數並沒有真正意義上改變記憶體的使用,只是將其狀態改變為“允許回收”,真正的回收在下面即將調用的 trimApplications() 函數中。
回收過程函數 trimApplications()
trimApplications() 函數的結構如下 :
清單 3. trimApplications 函數
private final void trimApplications() { synchronized (this) { // First remove any unused application processes whose package // has been removed. for (i=mRemovedProcesses.size()-1; i>=0; i--) { (1)//kill process; } if (!updateOomAdjLocked()) { (2)//do something default } // Finally, if there are too many activities now running, try to // finish as many as we can to get back down to the limit. (3)do something } }
清單 3 中的三個標序號的位置分別負責如下工作:
(1)當程式執行到 trimApplications() 之後,首先檢查 mRemovedProcesses 列表中的進程。mRemovedProcesses 列表中主要包含了 crash 的進程、5 秒內沒有響應並被使用者選在強制關閉的進程、以及應用開發這調用 killBackgroundProcess 想要殺死的進程。調用 Process.killProcess 將所有此類進程全部殺死。
(2)調用 updateOomAdjLocked() 函數,若成功返回,說明 Linux 核心支援 setOomAdj() 介面,updateOomAdjLocked 將修改 adj 的值並通知 linux 核心,核心根據 adj 值以及記憶體使用量情況動態管理進程資源(lowmemorykiller 和 oom_killer)。若 updateOomAdjLocked() 返回為假,則表示當前系統不支援 setOomAdj() 介面,將在本地進行預設的資源回收。
(3)最後,如果當前依然運行了過多的 Activity,對多餘的 Activity 進行回收。 trimApplications() 的大多數的代碼都在處理 Oom_killer 不存在情況下的預設資源回收,下面對其預設回收過程(即代碼清單中標記(2)的位置)進行進一步分析。其回收過程可大致描述如下。
步驟一,擷取當前所有啟動並執行進程 mLruProcesses,mLruProcesses 中的定序是按最近使用時間。對 mLruProcesses 中不能被關閉的進程進行計數,這些不能被關閉的進程包括運行 service 的進程,運行 broadcast receiver 的進程等,見如下代碼。
清單 4. 計數不能被關閉的進程
if (app.persistent || app.services.size() != 0 || app.curReceiver != null || app.persistentActivities > 0) { // Don't count processes holding services against our // maximum process count. numServiceProcs++; }
步驟二, 設當前最大運行進程數 curMaxProcs = curMaxProcs + numServiceProcs(即預設最大進程數與運行 Service 的進程數之和),如果當前進程的數量 mRemovedProcesses.size() 大於這個值,則遍曆所有當前啟動並執行進程,殺死合格那些進程並釋放記憶體。清理過程見清單 5(部分代碼省略)。從清單 5 的代碼中可以看出,進程被殺死的條件是:
①必須是非 persistent 進程,即非系統進程;
②必須是空進程,即進程中沒有任何 activity 存在。如果殺死存在 Activity 的進程,有可能關閉使用者正在使用的程式,或者使應用程式恢複的時延變大,從而影響使用者體驗;
③必須無 broadcast receiver。運行 broadcast receiver 一般都在等待一個事件的發生,使用者並不希望此類程式被系統強制關閉;
④進程中 service 的數量必須為 0。存在 service 的進程很有可能在為一個或者多個程式提供某種服務,如 GPS 定位服務。殺死此類進程將使其他進程無法正常服務。
以上條件缺一不可。
清單 5. 清理過程
if (!app.persistent && app.activities.size() == 0 && app.curReceiver == null && app.services.size() == 0) { if (app.pid > 0 && app.pid != MY_PID) { Process.killProcess(app.pid); } else { try { app.thread.scheduleExit(); } catch (Exception e) { // Ignore exceptions. } } // todo: For now we assume the application is not buggy // or evil, and will quit as a result of our request. // Eventually we need to drive this off of the death // notification, and kill the process if it takes too long. cleanUpApplicationRecordLocked(app, false, i); i--; }
步驟三,再次檢查當前啟動並執行進程,如果 mRemovedProcesses.size() 仍然大於 curMaxProcs,則放寬條件再次進行回收。判斷條件見代碼清單 6(部分代碼省略)。下面代碼中,布爾變數 canQuit 的值為真時,那麼這個進程可以被回收。canQuit 的取值分兩個步驟,首先是根據進程的屬性賦值。 1. 必須是非 persistent 進程,即非系統進程;2. 必須無 broadcast receiver;3. 進程中 service 的數量必須為 0;4. persistent 類型的 activity 數量為 0。與步驟二唯一的不同在第 4 條,這裡不要求進程是空進程,只要進程中沒有 persistent 類型的 Activity 就可以(Activity 是否是 persistent 類型在開發階段指定)。這些條件都滿足時,再檢查進程中每個 Activity 的屬性,當該進程中所有的 Activity 都還必須滿足三個條件:Activity 的狀態已經儲存,當前處在不可見狀態並且 Activity 已經 Stop。這時殺掉進程只會降低下次調用程式時的載入速度,下次啟動時將恢複到關閉之前的狀態,並不會在使用者體驗上造成致命的影響,所以,canQuit 置位為真。這種情況與步驟二的回收方式也有所不同,由於進程中 Activity 的數量不是 0,下一步需要對每個 activity 執行 destroyActivityLocked() 銷毀,最後才殺死進程。
清單 6. 執行 destroyActivityLocked() 銷毀
boolean canQuit = !app.persistent && app.curReceiver == null && app.services.size() == 0 && app.persistentActivities == 0; int NUMA = app.activities.size(); for (j=0; j 0 && app.pid != MY_PID) { Process.killProcess(app.pid); } cleanUpApplicationRecordLocked(app, false, i); i--; //dump(); }
步驟四,上面 3 個過程都是針對整個 process 進行的資源回收。在以上過程執行完畢之後,將在更小的粒度上對 Activity 的資源進行回收。與上面所述類似,列表 mLRUActivities 儲存了當前所有運行中的 Activity,定序同樣為最少訪問原則。mLRUActivities.size() 返回系統中啟動並執行 Activity 的數量,當其大於 MAX_ACTIVITIES(MAX_ACTIVITIES 是一個常量,一般值為 20,代表系統中最大允許同時存在的 Activity)時。將回收部分滿足條件的 Activity 以減少記憶體的使用。回收條件代碼清單 7 所示:
清單 7. 回收條件代碼
//Finally, if there are too many activities now running, try to // finish as many as we can to get back down to the limit. for ( i=0; i curMaxActivities; i++) { final HistoryRecord r = (HistoryRecord)mLRUActivities.get(i); // We can finish this one if we have its icicle saved and // it is not persistent. if ((r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped && !r.persistent && !r.finishing) { final int origSize = mLRUActivities.size(); destroyActivityLocked(r, true); if (origSize > mLRUActivities.size()) { i--; } } }
這裡回收的只是 Activity 的記憶體資源,並不會殺死進程,也不會影響進程的運行。當進程需要調用被殺掉的 Activity 時,可以從儲存的狀態中回複,當然可能需要相對長一點的時延。
回頁首
Linux 核心中的記憶體回收lowmemorykiller
上面提到,trimApplications() 函數中會執行一個叫做 updateOomAdjLocked() 的函數,如果返回 false,則執行預設回收,若返回 true 則不執行預設記憶體回收。updateOomAdjLocked 將針對每一個進程更新一個名為 adj 的變數,並將其告知 Linux 核心,核心維護一個包含 adj 的資料結構(即進程表),並通過 lowmemorykiller 檢查系統記憶體的使用方式,在記憶體不足的情況下殺死一些進程並釋放記憶體。下面將對這種 Android Framework 與 Linux 核心相配合的記憶體回收機制進行研究。
由於 Android 作業系統中的所有應用程式都運行在獨立的 Dalvik 虛擬機器環境中,Linux 核心無法獲知每個進程的運行狀態,也就無法為每個進程維護一個合適的 adj 值,因此,Android Application Framework 中必須提供一套機制以動態更新每個進程的 adj。這就是 updateOomAdjLocked()。
updateOomAdjLocked() 位於 ActivityManagerService 中,其主要作用是為進程選擇一個合適的 adj 值,並通知 Linux 核心更新這個值。updateOomAdjLocked 首先調用 computeOomAdjLocked() 初步計算 adj 的值,然後回到 updateOomAdjLocked() 對其值進行進一步修正。估算流程可參見代碼。
關於 adj,其定義在 task_struct->signal_struct->adj, 檔案 /kernel/include/linux/sched.h 中。實質為進程資料結構中的一個變數,用來表示發生 Out of Memory 時殺死進程的優先順序順序。lowmemorykiller 利用這個變數對進程的重要程度進行判斷,並在記憶體不足時釋放部分空間,其實現在檔案 /kernel/drivers/staging/android/lowmemorykiller.c 中。lowmemorykiller 定義了兩個數組:lowmem_adj 和 lowmem_minfree。其中 lowmen_adj 定義了一系列 adj 索引值,而 lowmem_minfree 的每個元素代表一個記憶體閾值。如下代碼中四個閾值分別是 6MB,8MB,16MB 和 64MB,分別代表當記憶體小於 64MB 時,adj 大於或等於 12 的那些進程將被殺死並回收,記憶體小於 16MB 時,adj 大於等於 6 的那些進程將被殺死並回收,記憶體小於 8MB 時,adj 大於等於 1 的那些進程將被殺死並回收,記憶體小於 6MB 時,adj 大於等於 0 的所有進程將被殺死並回收。核心中的每個進程都持有一個 adj,取值範圍 -17 到 15,值越小代表進程的重要性越高,回收優先順序越低,其中 -17 代表禁用自動回收。Android 系統中,只有 0-15 被使用。
清單 8. 每個進程都持有一個 adj
static int lowmem_adj[6] = { 0, 1, 6, 12, }; static int lowmem_adj_size = 4; static size_t lowmem_minfree[6] = { 3 * 512, /* 6MB */ 2 * 1024, /* 8MB */ 4 * 1024, /* 16MB */ 16 * 1024, /* 64MB */ }; static int lowmem_minfree_size = 4;
lowmemorykiller 註冊一個 lowmem_shrinker,lowmem_shrinker 利用了標準 Linux 核心中的 Cache Shrinker 來實現,當空閑記憶體頁面不足時,核心線程 kswapd 將用登入的 lowmem_shrinker 來回收記憶體頁面。
清單 9. 用登入的 lowmem_shrinker 來回收記憶體頁面
static struct shrinker lowmem_shrinker = { .shrink = lowmem_shrink, .seeks = DEFAULT_SEEKS * 16 }; static int __init lowmem_init(void) { task_free_register(&task_nb); register_shrinker(&lowmem_shrinker); return 0; }
lowmem_shrink 的代碼在函數 lowmem_shrink 中,下面給出該函數的主要結構。lowmem_shrink 根據上述規則遍曆所有進程,選出需要結束的進程,通過發送一個無法忽略的訊號 SIGKILL 強制結束這些進程
清單 10. 強制結束進程
static int lowmem_shrink(struct shrinker *s, int nr_to_scan, gfp_t gfp_mask)
{
for_each_process(p) {
//Select processes to be forced
}
if (selected) {
force_sig(SIGKILL, selected);
rem -= selected_tasksize;
} else
rem = -1;
return rem;
}
Oom_killer.
如果上述各種方法都無法釋放出足夠的記憶體空間,那麼當為新的進程分配應用程式時將發生 Out of Memory 異常,OOM_killer 將盡最後的努力殺掉一些進程來釋放空間。Android 中的 OOM_killer 繼承自標準 Linux 2.6 核心,用於分配記憶體時 Out of Memory 的處理。Android 並沒有對其實現方式進行修改。其位置在 linux/mm/oom_kill.c。 oom_killer 遍曆進程,並計算所有進程的 badness 值,選擇 badness 最大的那個進程將其殺掉。函數 badness 的聲明如下:
unsigned long badness(struct task_struct *p, unsigned long uptime) 函數 select_bad_process 返回將要殺掉的那個進程。
清單 11. 返回將要殺掉的進程
static struct task_struct *select_bad_process(unsigned long *ppoints, struct mem_cgroup *mem) { for_each_process(p) { points = badness(p, uptime.tv_sec); if (points > *ppoints || !chosen) { chosen = p; *ppoints = points; } } return chosen; }
最後,和 lowmemorykiller 一樣,通過發送 SIGKILL 結束選中的進程。由於 oom_killer 與標準 Linux 核心並無不同,這裡不再詳細研究。