Android 操做系統的內存回收機制

Android 是一款基於 Linux 內核,面向移動終端的操做系統。爲適應其做爲移動平臺操做系統的特殊須要,谷歌對其作了特別的設計與優化, html

使得其進程調度與資源管理與其餘平臺的 Linux 有明顯的區別。主要包含下面幾個層次: java

  1. Application Framework

    Application Framework 將整個操做系統分隔成兩個部分。對應用開發者而言,全部 APP 都是運行在 Application Framework 之上, linux

    而並不須要關心繫統底層的狀況。Application Framework 層爲應用開發者提供了豐富的應用編程接口,如 Activity Manager,Content Provider, android

    Notification Manager,以及各類窗口 Widget 資源等。在 Application Framework 層,Activity 是一個 APP 最基本的組成部分。通常每一個 Activity shell

    對應於屏幕上的一個視圖(或者說一屏),一個 APP 能夠有一個或者多個 Activity。應用程序被打包成 .apk 格式的文件,由 Dalvik VM 解釋執行。 編程

  2. Dalvik VM

    Dalvik 虛擬機採用寄存器架構,而不是 JVM 的棧結構。Java 程序編譯後的 .class 文件並不能在 Dalvik 中解釋執行。所以 Google 提供了一個 dx 工具, 數組

    用於將 .class 文件轉換成 Dalivk 可以識別的 .dex 格式。具體 Dalvik VM 的細節不是本文重點,如下再也不討論。 安全

  3. Linux kernel

    由上所述,全部的 APP 都是由 Java 代碼編寫並在 Dalvik VM 中獲得解釋執行。在 Android 操做系統中,每一個 Dalvik VM 的每一個 Instance 都對應於 數據結構

    Linux 內核中的一個進程。可使用 adb shell 工具查看系統中的當前進程。以下圖所示,Android2.3.3 啓動後內核中的進程列表。 架構



    圖 1. Android 2.3 中的進程列表(部分)
    圖 1. Android 2.3 中的進程列表(部分) 

    圖 1 中,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<NUMA && canQuit; j++) { HistoryRecord r = (HistoryRecord)app.activities.get(j); canQuit = (r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped; } if (canQuit) { // Finish all of the activities, and then the app itself. for (j=0; j<NUMA; j++) { HistoryRecord r = (HistoryRecord)app.activities.get(j); if (!r.finishing) { destroyActivityLocked(r, false); } r.resultTo = null; } if (app.pid > 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<mLRUActivities.size() && mLRUActivities.size() > 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 內核並沒有不一樣,這裏再也不詳細研究。

 

總結

  本文研究了 Android 操做系統上的內存回收機制。主要包括 Application Framework 層的默認回收以及 Linux 內核中的 lowmemorykiller、

OOM_killer。通常來講應用開發者並不須要控制或者修改系統的內存管理以及回收,可是深刻理解這些系統級的管理機制仍是必要的,

尤爲有助於更加合理地設計應用程序,使應用程序的進程在其生命週期內高效地運行。而系統級開發者若是想要對內存管理機制進行優化,

對原有機制的理解則是必不可少的重要前提。

相關文章
相關標籤/搜索