1. 程式人生 > >AMS之記憶體管理

AMS之記憶體管理

原文地址:http://www.jianshu.com/p/72045d243b44

參考資料地址:http://book.51cto.com/art/201109/291375.htm

記憶體管理包括兩個部分
1.當應用程式關閉後,後臺對應的程序並沒有真正退出,以便下次啟動時能夠快速啟動
2.當系統記憶體不夠用時,Ams會主動根據一定的規則退出優先順序較低的程序

1.關閉而不退出

每個應用程式的主體都對應一個ActivityThread類,該類初始化之後,就進入Looper.loop()函式中無限迴圈,以後則依靠訊息機制來執行,當有訊息處理時處理訊息,而沒有訊息時程序會進入到sleep狀態。

public static void main(String[] args) {
//looper…
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
//handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, “ActivityThread”));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();//進入迴圈 
throw new RuntimeException("Main thread loop unexpectedly exited");

}

loop方法中執行for迴圈

public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();

final long ident = Binder.clearCallingIdentity();

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    } 
    try {
        msg.target.dispatchMessage(msg);
    } finally { 
    } 
    msg.recycleUnchecked();
}

}

queue.next方法中,會從訊息佇列中取出訊息,如果沒有訊息,函式內部會導致當前執行緒程序進入sleep狀態,並且只有新訊息到達後next函式才繼續執行並返回新的訊息。queue.next會在三種情況下被喚醒
1.定時器中斷。如果應用程式中設定了定時器任務,那麼定時器發生時,作業系統會回撥該定時器任務,可以在定時器任務中向looper傳送一條訊息,從而next方法會被喚醒,並返回得到的訊息
2.使用者按鍵訊息。當有使用者按鍵訊息時,wms中專門處理訊息的執行緒會把這個訊息傳送到looper中
3.Binder中斷。當binder驅動接收到binder訊息,並派發到客戶端的binder物件後,binder執行緒開始執行,如果在binder的訊息處理中向looper發一條訊息,next就會繼續執行。

大多數應用程式屬於訊息驅動模式,沒有訊息程式就會sleep,知道產生訊息
2.android與Linux的配合

系統記憶體是否夠用屬於linux核心的記憶體管理控制的事情,ams是無法預知的。
2.1 為什麼ams不能判斷記憶體是否夠用

1.應用程式申請記憶體不會通知Ams,所以Ams無法感知應用程式申請記憶體的情況,除非每次應用程式發生oom時通知ams,但系統並沒有這麼做
2.java虛擬機器執行時都有各自獨立的記憶體空間,應用程式A發生oom並不意味著應用B也會發生oom,很有可能是A程式用光了自己記憶體的上限,而系統記憶體還是有的
單純AMS是無法感知系統記憶體是否低的
2.2 結合linux

android底層的linux,由於沒有采用磁碟虛擬記憶體機制,所以應用程式能夠使用的記憶體大小完全取決於設計實體記憶體的大小,所以記憶體低的含義就是實際實體記憶體被用的所剩無幾了

TU 10-19

在android中運行了一個OOM程序,該程序啟動時首先會想linux核心中把自己註冊為一個OOM Killer,即當Linux核心的記憶體管理模組檢測到系統記憶體低的時候就會通知已經註冊的OOM程序,然後OOMKiller就可以根據規則進行記憶體釋放了。
OOMKiller 在執行時,Ams需要把每一個應用程式的oom_adj值告知給Killer,值越低就越重要。
Ams僅僅是根據應用的前後臺關係分配給應用一個oom_adj值,剩下的就是OOM Killer的事情了
3.關閉程式

第一種情況:從呼叫startActivity開始,一般情況下,當前會有正在執行的activity,所以需要暫停當前activity,暫停完畢後,Ams會收到一個Binder訊息,並開始從completePaused執行,在該函式中,由於上一個activity沒有finishing,僅僅是stop,所以這裡會把上一個activity加入到mStoppingActivities中,當目標Activity啟動後,會向Ams傳送一個請求進行記憶體回收的訊息,這導致Ams內部呼叫activityIdleInternal方法,該方法會首先處理mStoppingActivities中的Activity物件,這會呼叫到stopActivityLocked方法,該方法通過IPC呼叫,通知應用程序stop指定的activity,stop完成後再通知Ams,於是Ams從activityStopped處開始執行,而這會呼叫trimApplications方法,trimApplications方法中會執行和記憶體相關的操作

第二種情況:按下Back鍵,會呼叫finishActivityLocked,然後把Activity的finishing標誌設為true,然後再呼叫startPausingLocked,當目標actiity完成暫停後,就會通知Ams,此時Ams從completePaused開始執行,由於此時暫停的Activity的finising狀態已經變為true,所以會執行finishingActivtyLocked。

第三種情況:當Activity啟動後,向Ams傳送一個Idle訊息,這會導致Ams開始執行activityIdleInternal方法,該方法首先處理mStoppingActivities中的物件,接著處理mFinishingActivities列表,然後再呼叫trimApplications

這三種情況包括stop和destroy,對應onStop和onDestroy方法

從記憶體回收角度看,釋放記憶體的地點包括三個

1.Ams中進行,也就是當系統記憶體低時,優先釋放沒有任何Activity的程序,然後釋放非前臺Activity對應的程序
2.第二個是在OOMKiller中,此時Ams要告知OOMKiller各個應用程序的優先順序,然後OOMKiller就會呼叫Linux內部的程序管理方法殺死優先順序較低的程序
3.在應用程序本身,當Ams認為目標程序需要被殺死,首先會通知目標程序進行記憶體釋放,包括呼叫目標程序的scheduleLowMemory和procssInBackground方法。

4.釋放記憶體
4.1 activityIdleInternal

TU 10-12
4.1.1 最常見的呼叫到該方法的情況是目標activity啟動後。

當指定的activity啟動後會報告ams,此時上一個activity僅僅處於pause狀態,還有stop或destroy,在目標activity的啟動時會通過如下程式碼傳送一個idle訊息

handleResumeActivity{
……
Looper.myQueue().addIdleHandler(new Idler());
……
}

handleMssage{
……
am.activityIdle(a.token, a.createdConfig, stopProfiling);
……
}

4.1.2 視窗從非顯示變成顯示windowVisible,會告知ams

視窗顯示後如果沒有使用者操作的話,可以被視為空閒狀態,這點和第一種情況很類似。對於一般的Activity而言,resume後要通知ams進行空閒回收,而其他視窗顯示出來也要通知ams進行空閒回收,豈不是有點重複?原因是有些視窗僅僅是視窗,不對應任何activity,並且activityIdle方法內部並不僅僅是回收剛剛暫停的activity,而是整個系統內部的狀態檢查
4.1.3 completePausedLocked方法中

傳送IDLE_NOW_MSG
4.1.4 completeResumeLocked

檢測啟動超時,如果在指定時間內不能啟動指定activity,則會呼叫activityIdleInternal釋放相關資源。
4.1.5 activityIdleInternal 方法分析

該方法中並不涉及真正的回收記憶體的操作,真正回收記憶體的操作是通過呼叫trimApplication來實現的

1.通知所有需要回收記憶體的客戶程序進行記憶體回收
2.取出mStoppingActivities列表中的內容,並存放到臨時表stops中,取出mFinishingActivities中的內容放入臨時表finishes中,然後刪除原有的記錄
3.首先對stops表進行處理,處理的過程中有一個判斷activity的finishing狀態的條件,這個表中的activity一般情況下finishing為false,stopped為true,但是不一定finishing狀態全是false,如按下Back鍵,finishing狀態就是true,接收在completePaused中呼叫到了finishCurrentActivity函式,該函式在把指定的activity放到mStoppingActivities中;在這步處理中如果finishing為false,則呼叫stopActivityLocked通知客戶端stop該activity,如果finish狀態為true,則需要判斷是否需要立即停止,如果要立即停止,就呼叫destroyActivityLocked通知目標呼叫onDestroy,如果不需要立即停止,就先呼叫resumeTopActivity執行下一個activity
4.對finishes列表中的物件進行處理,由於finishes列表中物件的finishing狀態都是true,所以可以直接呼叫destroyActivityLocked通知客戶程序銷燬目標activity。
5.呼叫trimApplication

4.2 trimApplications

有兩處呼叫該方法的地方activityIdleInternal和stopActivityLocked

final void trimApplications() {
    synchronized (this) {
        int i;

        // First remove any unused application processes whose package
        // has been removed.
        for (i=mRemovedProcesses.size()-1; i>=0; i--) {
            final ProcessRecord app = mRemovedProcesses.get(i);
            if (app.activities.size() == 0
                    && app.curReceiver == null && app.services.size() == 0) {
                Slog.i(
                    TAG, "Exiting empty application process "
                    + app.processName + " ("
                    + (app.thread != null ? app.thread.asBinder() : null)
                    + ")\n");
                if (app.pid > 0 && app.pid != MY_PID) {
                    app.kill("empty", false);
                } else {
                    try {
                        app.thread.scheduleExit();
                    } catch (Exception e) {
                        // Ignore exceptions.
                    }
                }
                cleanUpApplicationRecordLocked(app, false, true, -1);
                mRemovedProcesses.remove(i);

                if (app.persistent) {
                    addAppLocked(app.info, false, null /* ABI override */);
                }
            }
        }

        // Now update the oom adj for all processes.
        updateOomAdjLocked();
    }
}

1.刪除mRemovedProcesses列表中包含的應用程序,該列表的內容來自四種情況;第一種,當某個程序crash後,會被新增進這個表;第二種,某個程式的ui執行緒在5秒內沒有響應,系統彈出一個anr對話方塊,此時如果使用者選擇強制關閉,該程序會被新增進這個列表;第三種是當呼叫ams提供的killBackgroundProcess方法時(呼叫者需要有KILL_BACKGROUND_PROCESS許可權);第四種,當系統啟動時,Ams的systemReady方法中,如果發現啟動了非persistant型別的程序,則把這些程序加入到表中。
2.呼叫updateOomAdjLocked,該方法的作用是告訴OOMKiller指定程序的優先順序,值越小越重要,該函式的返回值是boolean型別,如果底層linux包含OOMKiller則返回true,否則返回false
3.如果不支援OOM,則執行Ams的規則,也就是優先殺死後臺activity等
4.最後,無論是使用oom還是ams的規則,如果殺死後臺程序之後,此時執行的activity的數量依然超過MAX_ACTIVITIES(20),則需要繼續銷燬滿足以下三個條件的activity;第一,已經stop還沒有finishing的;第二,必須是不可見的,也就是說該activity視窗上面有其他全屏視窗;第三,不能使persistent型別的

trimApplications之後,如果記憶體依然不夠,那就無能為力了
4.3 updateOomAdjLocked

該方法的作用是告訴OOM指定程序的優先順序
4.3.1 呼叫computeOomAdjLocked

先獲取當前程序,然後遍歷lru表中的資料,對每個資料執行computeOomAjdLocked方法

final void updateOomAdjLocked() {
final ActivityRecord TOP_ACT = resumedAppLocked();
final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
final long now = SystemClock.uptimeMillis();
final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
final int N = mLruProcesses.size();
//……
for (int i=N-1; i>=0; i–) {
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm && app.thread != null) {
app.procStateChanged = false;
computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);

4.3.2 computeOomAdjLocked
4.3.2.1 computeOomAdjLocked 關於activity的處理的第一階段

1.判斷該程序是否是TOP_APP,如果是優先順序自然最高
2.判斷該程序中是否包含instrumentationClass,該值一般在單元測試程序中存在,如果是優先順序最高
3.判斷該程序是否包含持久的activity物件,如果有優先順序最高
4.判斷改進陳是否包含正在執行的receiver物件,如果有優先順序最高
5.判斷當前是否有正在執行的service物件,如果有則優先順序最高
這五種情況adj都是FOREGROUND_APP_ADJ,也就是說這五種情況,指定的客戶端程序都不能被殺死,其優先順序最高而且平等

//computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
//app是從lru列表中取出的,TOP_APP是當前正在執行activity所屬的app,now是當前時間
private final int computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
boolean doingAll, long now) {
if (mAdjSeq == app.adjSeq) {
return app.curRawAdj;
}

if (app.thread == null) {
    app.adjSeq = mAdjSeq;
    app.curSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
    app.curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
    return (app.curAdj=app.curRawAdj=ProcessList.CACHED_APP_MAX_ADJ);
}

if (app == TOP_APP) {//app就是當前正在執行的activity也就是TOP_APP,
    // The last app on the list is the foreground app.//
    adj = ProcessList.FOREGROUND_APP_ADJ;//設定為前臺程序FOREGROUND_APP_ADJ
    schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
    app.adjType = "top-activity";
    foregroundActivities = true;
    procState = PROCESS_STATE_CUR_TOP;
} else if (app.instrumentationClass != null) {//這個一般在單元測試程序中存在
    // Don't want to kill running instrumentation.
    adj = ProcessList.FOREGROUND_APP_ADJ;//設定為前臺程序FOREGROUND_APP_ADJ
    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
    app.adjType = "instrumentation";//type
    procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
} else if ((queue = isReceivingBroadcast(app)) != null) {
    //判斷app中是否有正在執行的reiceiver物件,如果有也是設定為前臺程序
    adj = ProcessList.FOREGROUND_APP_ADJ;
    schedGroup = (queue == mFgBroadcastQueue)
            ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
    app.adjType = "broadcast";//adjType為broadcast
    procState = ActivityManager.PROCESS_STATE_RECEIVER;
} else if (app.executingServices.size() > 0) {
    // An app that is currently executing a service callback also counts as being in the foreground.
    // app如果當前正在執行service裡的回撥方法,也算前臺程序
    adj = ProcessList.FOREGROUND_APP_ADJ;
    schedGroup = app.execServicesFg ?
            ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
    app.adjType = "exec-service";//ajdtype是exec-service
    procState = ActivityManager.PROCESS_STATE_SERVICE; 
}

4.3.2.2 computeOomAdjLocked 關於activity的處理的第二階段

1.判斷指定的程序是否正在呼叫前臺程序的service物件,如果是的話,則其優先順序為VISIBLE_APP_ADJ,這種情況的程序優先順序略低於前臺程序,但因為它和前臺程序正處於互動,所以有VISIBLE_APP_ADJ優先順序
2.判斷forcingToForeground變數是否為空,如果不為空,說明該程序正在被使用者強制調到前臺,這種情況是瞬間的,但如果是這種情況,優先順序也是比較高的VISIBLE_APP_ADJ

    if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
            || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
        if (app.foregroundServices) {
            // The user is aware of this app, so make it visible.
            adj = ProcessList.PERCEPTIBLE_APP_ADJ;
            procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
            app.cached = false;
            app.adjType = "fg-service";
            schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
        } else if (app.forcingToForeground != null) {
            // The user is aware of this app, so make it visible.
            adj = ProcessList.PERCEPTIBLE_APP_ADJ;
            procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
            app.cached = false;
            app.adjType = "force-fg";
            app.adjSource = app.forcingToForeground;
            schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
        }
    }

4.3.2.3 computeOomAdjLocked 關於activity的處理的第三階段

    if (app == mHomeProcess) {
        if (adj > ProcessList.HOME_APP_ADJ) {
            // This process is hosting what we currently consider to be the
            // home app, so we don't want to let it go into the background.
            adj = ProcessList.HOME_APP_ADJ;
            schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
            app.cached = false;
            app.adjType = "home";
        }
        if (procState > ActivityManager.PROCESS_STATE_HOME) {
            procState = ActivityManager.PROCESS_STATE_HOME;
        }
    }

判斷是否是Home程序,如果是的話,優先順序調整為HOME_APP_ADJ

4.3.2.6 computeOomAdjLocked 關於activity的處理的第六階段

判斷是否為mBackupTarget程序,由於備份程序要在後臺持續收集資料,因此,優先順序高於一般程序,低於visible程序

    if (mBackupTarget != null && app == mBackupTarget.app) {
        // If possible we want to avoid killing apps while they're being backed up
        if (adj > ProcessList.BACKUP_APP_ADJ) {
            if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
            adj = ProcessList.BACKUP_APP_ADJ;
            if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
                procState = ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
            }
            app.adjType = "backup";
            app.cached = false;
        }
        if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
            procState = ActivityManager.PROCESS_STATE_BACKUP;
        }
    }

4.3.2.7 computeOomAdjLocked 關於service的處理

1.迴圈針對每個service的狀態調整adj,在每個service物件中判斷service是否被啟動過,如果為true表示被啟動過,並且還沒超出MAS_SERVICE_INACTIVITY,則設定優先順序為SECONDARY_SERVICE_ADJ

2.處理和該service有連線的客戶端,並根據客戶端的優先順序調整service的優先順序,首先判斷客戶端是否和service處於統一程序,如果是,就不調整
3.判斷service是否以BIND_AUTO_CREATE方式啟動,如果是說明其重要程度取決於客戶端,否則不做任何處理

4.經過一系列呼叫,結果是,如果當前app沒有client重要,則把重要性調整到和client相同重要,否則保持當前的重要性

5.判斷客戶端是否有對應的activity,並且該activtity是否處於resumed或者pausing狀態,如果是的話,則說明這個客戶端連結很重要,同樣app也就很重要,優先順序重新設定為FOREGROUND_APP_ADJ
4.3.2.8 computeOomAdjLocked 關於provider的處理

和處理service基本一致,不同的是,ContentProviderRecord物件中有一個externals物件,這是一個int值,代表了連線該provider的非framework的程序id,也就是說可以使用native寫一個linux程式,使用android所提供的provider底層庫訪問provider,而這個external就是非framework程序的id號,如果這種情況存在,則調整adj為最高。
4.4 ams內部記憶體回收的規則
4.4.1 程序類別

前臺程序(foreground process)

正在和使用者互動的activity,即執行了onResume
包含一個service,該service正在服務於和使用者互動的activity
包含一個service,該servie正在執行oncreate,onstart或者ondestroy
包含一個receiver,正在執行onReceive方法

可視程序(visible process)

沒有和使用者互動,但使用者可以看見該activity的視窗,比如一個activity上面彈出一個對話方塊的情況
包含一個service,該service服務於可視的activity,也就是看得見卻不能互動的actiity

服務程序(service process)

使用startService啟動的service物件,所在的程序都是服務程序,當然如果該service滿足上面的條件,則會相應的提升優先順序

後臺程序(background process)

不滿足上面的三個條件,同時該程序中還包含一些不可見的activity,這些程序不影響正在和使用者互動的activity

空程序(empty process)

不包含任何component,之所以保留它是為了減少重新建立該程序的開銷,建立空程序的過程包括建立程序和載入應用的資原始檔,都是很耗時的。

如果不支援oom,則ams使用自己的規則

4.5 客戶程序回收記憶體

1.Ams呼叫scheduleAppGcLocked方法時會通知指定的app物件進行記憶體回收。
該函式首先檢查app上一次進行gc的時間,並和當前時間進行對比,如果還沒超過最小間隔,則將指定的app加入到mProcessesToGc列表中,
2.如果超過了最小時間間隔,則從mProcessesToGc列表中取出下一個app,併發送一個延遲訊息,處理該訊息的函式是performAppGcsIfAppropriate函式,該函式內部先判斷是否合適進行記憶體回收,如果可以則呼叫performAppGcsLoceked,否則再次傳送一個非同步訊息。
3.在performAppGcsLoceked內部,使用while迴圈,在mProcessesToGc列表中逐個取出每個需要進行gc的ProcessRecord物件,並對該物件執行performAppGcLocked(app)方法
4.performAppGcLocked先判斷reportLowMemory標誌是否為true,如果是則呼叫app.thread.scheduleLowMemory方法處理,否則呼叫app.thread.processInBackground方法
4.5.1 scheduleLowMemory

1.scheduleLowMemory會發一個訊息,處理該訊息的函式是handleLowMemory
2.回撥該程序所包含的所有元件的onLowMemory方法,
3.呼叫SQLiteDatabase的靜態方法releaseMemory釋放SQLite模組佔用的記憶體。
4.使用Canvas的freeCaches釋放應用中所有的canvas物件
5.呼叫BinderInternal.forceGc(“mem”)釋放該程序的Binder物件
4.5.2 processInBackground

1.呼叫BinderInternal.forceGc(“mem”)釋放該程序的Binder物件

作者:xihe
連結:http://www.jianshu.com/p/72045d243b44
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。