Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 操作系統的內存回收機制

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

編輯:關於Android編程

 

Android APP 的運行環境

Android 是一款基於 Linux 內核,面向移動終端的操作系統。為適應其作為移動平台操作系統的特殊需要,谷歌對其做了特別的設計與優化,使得其進程調度與資源管理與其他平台的 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 內核並無不同,這裡不再詳細研究。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved