??嵌入式裝備的1個普遍特點(diǎn)是內(nèi)存容量相對有限。當(dāng)運(yùn)行的程序超過1定數(shù)量時,或觸及復(fù)雜的計(jì)算時,極可能出現(xiàn)內(nèi)存不足,進(jìn)而致使系統(tǒng)卡頓的現(xiàn)象。Android 系統(tǒng)也不例外,它一樣面臨著裝備物理內(nèi)存短缺的窘境。對已啟動過1次的Android程序,再1次啟動所花的時間會明顯減少。緣由在于Android系統(tǒng)其實(shí)不馬上清算那些已”淡出視野”的程序(比如你調(diào)用Activity.finish退出UI界面)。它們在1定的時間里依然駐留在內(nèi)存中。這樣做的好處是明顯的,即下1次啟動不需要再為程序重新創(chuàng)建1個進(jìn)程;壞處就是,加大了內(nèi)存OOM的幾率。
??Android是基于Linux的,而Linux底層內(nèi)核有自己的內(nèi)存監(jiān)控機(jī)制,即OOMKiller。1旦發(fā)現(xiàn)系統(tǒng)的可用內(nèi)存到達(dá)臨界值,這個OOM的管理者就會自動跳出來清算內(nèi)存。
OOMKiller有不同的策略和不同的處理手段。它的核心思想以下:
依照優(yōu)先級順序,從低到高逐漸殺掉進(jìn)程,回收內(nèi)存。
優(yōu)先級的設(shè)定策略主要斟酌兩個方面:1個是要斟酌對系統(tǒng)的侵害程度(例如系統(tǒng)的核心進(jìn)程,優(yōu)先級通常較高),另外一方面也希望盡量多地釋放無用內(nèi)存。1個公道的策略最少需要斟酌以下幾個因素:
??內(nèi)核所管理的進(jìn)程都有1個衡量其oom權(quán)重的值,存儲在/proc/< PID >/oom_adj中。根據(jù)這1權(quán)重值和上面所提及的若干其他因素,系統(tǒng)會實(shí)時給每一個進(jìn)程評分,以決定OOM時應(yīng)當(dāng)殺死哪些進(jìn)程。
這個值存儲在/proc/< PID >/oom_score中。
oom_score分?jǐn)?shù)越低的進(jìn)程,被殺死的幾率越小,或說被殺死的時間越晚。
下面展現(xiàn)了PID為5912的NetworkManager進(jìn)程的oom_adj 和oom_score,可以看到分?jǐn)?shù)很低,說明此進(jìn)程10分重要,1般不會被系統(tǒng)殺死。
基于Linux內(nèi)核OOM Killer的核心思想,Android 系統(tǒng)擴(kuò)大出了自己的內(nèi)存監(jiān)控體系。由于Linux下的內(nèi)存殺手需要等到系統(tǒng)資源”瀕臨絕境”的情況下才會產(chǎn)生效果,而Android則實(shí)現(xiàn)了自己的Killer.
Android 系統(tǒng)為此開發(fā)了1個專門的驅(qū)動,名為Low Memory Killer(LMK)。源碼路徑在內(nèi)核工程的 drivers/staging/android/Lowmemorykiller.c中。
它的驅(qū)動加載函數(shù)以下:
static int __init lowmem_init(void)
{
register_shrinker(&lowmem_shrinker);
return 0;
}
可見LMK向內(nèi)核線程注冊了1個shrinker的監(jiān)聽回調(diào),實(shí)現(xiàn)體為lowmem_shrinker。當(dāng)系統(tǒng)的空閑頁面低于1定閾值時,這個回調(diào)就會被履行。
Lowmemorykiller.c 中定義了兩個數(shù)組,分別以下:
static short lowmem_adj[6] = {
0,
1,
6,
12,
};
static int lowmem_adj_size = 4;//下面的數(shù)值以此為單位(頁大小)
static int lowmem_minfree[6] = {
3 * 512, /* 6MB */
2 * 1024, /* 8MB */
4 * 1024, /* 16MB */
16 * 1024, /* 64MB */
};
第1個數(shù)組lowmem_adj最多有6個元素,默許只定義了4個,它表示可用容量處于”某層級”時需要被處理的adj值;第2個數(shù)組則是對”層級”的描寫。這樣說可能不清楚,舉個例子,lowmem_minfree 的第1個元素是3*512,3*512*lowmem_adj_size=6MB.意味著當(dāng)可用內(nèi)存小于6MB時,Killer需要清算adj的值為0(即lowmem_adj的第1個元素)以下的那些進(jìn)程。其中adj的取值范圍是⑴7~15,數(shù)字越小表示進(jìn)程級別越高,通常只有0⑴5被使用。
下圖是LWK機(jī)制的實(shí)現(xiàn)簡圖。
這兩個數(shù)組只是系統(tǒng)的預(yù)定義值,我們可以根據(jù)項(xiàng)目的實(shí)際需求來定制。
Android系統(tǒng)提供了相應(yīng)的文件來供我們修改這兩組值。
路徑以下:
/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree
可以在 init.rc(系統(tǒng)啟動時,由init進(jìn)程解析的第1個腳本)中加入以下語句。init.rc路徑為/system/core/rootdir/init.rc
/sys/module/lowmemorykiller/parameters/adj 0,8
/sys/module/lowmemorykiller/parameters/minfree 1024,4096
進(jìn)程omm_adj的大小跟進(jìn)程的類型和進(jìn)程被調(diào)度的次序有關(guān)。
在Android中,進(jìn)程主要分為以下幾個種類:
1. 前臺進(jìn)程(foreground)
??目前正在屏幕上顯示的進(jìn)程和1些系統(tǒng)進(jìn)程。舉例來講,Dialer,Storage,Google Search等系統(tǒng)進(jìn)程就是前臺進(jìn)程;再舉例來講,當(dāng)你運(yùn)行1個程序,如閱讀器,當(dāng)閱讀器界面在前臺顯示時,閱讀器屬于前臺進(jìn)程(foreground),但1旦你按home回到主界面,閱讀器就變成了后臺程序(background)。我們最不希望終止的進(jìn)程就是前臺進(jìn)程。
2. 可見進(jìn)程(visible)
??可見進(jìn)程是1些不再前臺,但用戶仍然可見的進(jìn)程,舉個例來講:widget、輸入法等,都屬于visible。這部份進(jìn)程雖然不在前臺,但與我們的使用也密切相干,我們也不希望它們被終止(你肯定不希望時鐘、天氣,新聞等widget被終止,那它們將沒法同步,你也不希望輸入法被終止,否則你每次輸入時都需要重新啟動輸入法)。
3. 桌面進(jìn)程(home app)
??即launcher,保證在多任務(wù)切換以后,可以快速返回到home界面而不需重新加載launcher。
4. 次要服務(wù)(secondary server)
??目前正在運(yùn)行的1些服務(wù)(主要服務(wù),如撥號等,是不可能被進(jìn)程管理終止的,故這里只談次要服務(wù)),舉例來講:谷歌企業(yè)套件,Gmail內(nèi)部存儲,聯(lián)系人內(nèi)部存儲等。這部份服務(wù)雖然屬于次要服務(wù),但很1些系統(tǒng)功能仍然息息相干,我們經(jīng)常需要用到它們,所以也不太希望他們被終止。
5. 后臺進(jìn)程(hidden)
??即是后臺進(jìn)程(background),就是我們通常意義上理解的啟動后被切換到后臺的進(jìn)程,如閱讀器,瀏覽器等。當(dāng)程序顯示在屏幕上時,他所運(yùn)行的進(jìn)程即為前臺進(jìn)程(foreground),1旦我們按home返回主界面(注意是按home,不是按back),程序就駐留在后臺,成為后臺進(jìn)程(background)。后臺進(jìn)程的管理策略有多種:有較為積極的方式,1旦程序到達(dá)后臺立即終止,這類方式會提高程序的運(yùn)行速度,但沒法加速程序的再次啟動;也有較消極的方式,盡量多的保存后臺程序,雖然可能會影響到單個程序的運(yùn)行速度,但在再次啟動已啟動的程序時,速度會有所提升。這里就需要用戶根據(jù)自己的使用習(xí)慣找到1個平衡點(diǎn)。
6. 內(nèi)容供應(yīng)節(jié)點(diǎn)(content provider)
沒有程序?qū)嶓w,進(jìn)提供內(nèi)容供別的程序去用的,比如日歷供應(yīng)節(jié)點(diǎn),郵件供應(yīng)節(jié)點(diǎn)等。在終止進(jìn)程時,這類程序應(yīng)當(dāng)有較高的優(yōu)先權(quán)。
7. 空進(jìn)程(empty)
??沒有任何東西在內(nèi)運(yùn)行的進(jìn)程,有些程序,比如BTE,在程序退出后,仍然會在進(jìn)程中駐留1個空進(jìn)程,這個進(jìn)程里沒有任何數(shù)據(jù)在運(yùn)行,作用常常是提高該程序下次的啟動速度或記錄程序的1些歷史信息。這部份進(jìn)程無疑是應(yīng)當(dāng)最早終止的。
在AMS 用于處理進(jìn)程的相干代碼文件ProcessList.java 中,定義了不同類型的adj值,以下所示:
/**
*省略其它代碼
*/
//未知的adj
static final int UNKNOWN_ADJ = 16;
static final int CACHED_APP_MAX_ADJ = 15;
static final int CACHED_APP_MIN_ADJ = 9;
//B list of service ,和A list相比,對用戶黏合度小1些
static final int SERVICE_B_ADJ = 8;
//用戶前1次交互的進(jìn)程
static final int PREVIOUS_APP_ADJ = 7;
//Launcher進(jìn)程
static final int HOME_APP_ADJ = 6;
//運(yùn)行了application service的進(jìn)程
static final int SERVICE_ADJ = 5;
//重量級利用程序進(jìn)程
static final int HEAVY_WEIGHT_APP_ADJ = 4;
//用于承載backup相干操作的進(jìn)程
static final int BACKUP_APP_ADJ = 3;
//這類進(jìn)程能被用戶感覺到但不可見,如后臺運(yùn)行的音樂播放器
static final int PERCEPTIBLE_APP_ADJ = 2;
//有前臺可見的Activity進(jìn)程,如果輕易殺死這類進(jìn)程,將嚴(yán)重影響用戶體驗(yàn)
static final int VISIBLE_APP_ADJ = 1;
//當(dāng)前正在運(yùn)行的那個進(jìn)程,即用戶正在交互的程序
static final int FOREGROUND_APP_ADJ = 0;
static final int PERSISTENT_SERVICE_ADJ = -11;
//persistent性質(zhì)的進(jìn)程,如telephony
static final int PERSISTENT_PROC_ADJ = -12;
//系統(tǒng)進(jìn)程
static final int SYSTEM_ADJ = -16;
/**
*省略其它代碼
*/
上面定義的adj數(shù)值來看,adj越小表示進(jìn)程類型就越重要,特別的,系統(tǒng)進(jìn)程的默許oom_adj 為⑴6,這類進(jìn)程永久不會被殺死。
還需要注意的是updateOomLevels函數(shù),內(nèi)部原理是通過寫上面兩個文件來實(shí)現(xiàn),AMS 會根據(jù)系統(tǒng)確當(dāng)前配置自動修正adj和minfree,以盡量適配不同的硬件。函數(shù)源碼以下所示:
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
// Scale buckets from avail memory: at 300MB we use the lowest values to
// 700MB or more for the top values.
float scaleMem = ((float)(mTotalMemMb-300))/(700-300);
// Scale buckets from screen size.
int minSize = 480*800; // 384000
int maxSize = 1280*800; // 1024000 230400 870400 .264
float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
if (false) {
Slog.i("XXXXXX", "scaleMem=" + scaleMem);
Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth
+ " dh=" + displayHeight);
}
float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
if (scale < 0) scale = 0;
else if (scale > 1) scale = 1;
int minfree_adj = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
int minfree_abs = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
if (false) {
Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs);
}
// We've now baked in the increase to the basic oom values above, since
// they seem to be useful more generally for devices that are tight on
// memory than just for 64 bit. This should probably have some more
// tuning done, so not deleting it quite yet...
final boolean is64bit = false; //Build.SUPPORTED_64_BIT_ABIS.length > 0;
for (int i=0; i<mOomAdj.length; i++) {
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
mOomMinFree[i] = (int)(low + ((high-low)*scale));
if (is64bit) {
// On 64 bit devices, we consume more baseline RAM, because 64 bit is cool!
// To avoid being all pagey and stuff, scale up the memory levels to
// give us some breathing room.
mOomMinFree[i] = (3*mOomMinFree[i])/2;
}
}
if (minfree_abs >= 0) {
for (int i=0; i<mOomAdj.length; i++) {
mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
}
}
if (minfree_adj != 0) {
for (int i=0; i<mOomAdj.length; i++) {
mOomMinFree[i] += (int)((float)minfree_adj * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
if (mOomMinFree[i] < 0) {
mOomMinFree[i] = 0;
}
}
}
// The maximum size we will restore a process from cached to background, when under
// memory duress, is 1/3 the size we have reserved for kernel caches and other overhead
// before killing background processes.
mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024) / 3;
// Ask the kernel to try to keep enough memory free to allocate 3 full
// screen 32bpp buffers without entering direct reclaim.
int reserve = displayWidth * displayHeight * 4 * 3 / 1024;
int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust);
int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute);
if (reserve_abs >= 0) {
reserve = reserve_abs;
}
if (reserve_adj != 0) {
reserve += reserve_adj;
if (reserve < 0) {
reserve = 0;
}
}
if (write) {
ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
buf.putInt(LMK_TARGET);
for (int i=0; i<mOomAdj.length; i++) {
buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
buf.putInt(mOomAdj[i]);
}
writeLmkd(buf);
SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
}
// GB: 2048,3072,4096,6144,7168,8192
// HC: 8192,10240,12288,14336,16384,20480
}
除系統(tǒng)的評定標(biāo)準(zhǔn),我們也能夠用自己的方式來改變進(jìn)程的權(quán)重值。
1.寫文件
??和上述提到的adj和minfree類似,進(jìn)程的oom_adj也能夠通過寫文件的情勢來修改,路徑為/proc/< PID >/oom_adj。比如init.rc中有以下語句
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj ⑴6
PID 為1的進(jìn)程為init程序,將此進(jìn)程的oom_adj 設(shè)置為⑴6,保證它不會被殺死。
2.設(shè)置android:persistent
??對某些非常重要的程序,不希望它被系統(tǒng)殺死。在AndroidMainifest.xml文件中給application 標(biāo)簽添加”android:persistent=true”屬性,將利用程序設(shè)置為常駐內(nèi)存,但需要特別注意,如果利用程序本不夠完善,而系統(tǒng)又不能正常回收,那末會致使意想不到的問題。
參考資料
《深入理解Android內(nèi)核設(shè)計(jì)思想》