Android Low Memory Killer原理分析

Android 的設(shè)計(jì)理念之一,便是應(yīng)用程序退出,但進(jìn)程還會(huì)繼續(xù)存在系統(tǒng)以便再次啟動(dòng)時(shí)提高響應(yīng)時(shí)間. 這樣的設(shè)計(jì)會(huì)帶來一個(gè)問題, 每個(gè)進(jìn)程都有自己獨(dú)立的內(nèi)存地址空間,隨著應(yīng)用打開數(shù)量的增多,系統(tǒng)已使用的內(nèi)存越來越大,就很有可能導(dǎo)致系統(tǒng)內(nèi)存不足, 那么需要一個(gè)能管理所有進(jìn)程,根據(jù)一定策略來釋放進(jìn)程的策略,這便有了lmk,全稱為L(zhǎng)owMemoryKiller(低內(nèi)存殺手),lmkd來決定什么 時(shí)間殺掉什么進(jìn)程.

Android基于Linux的系統(tǒng),其實(shí)Linux有類似的內(nèi)存管理策略——OOM killer,全稱(Out Of Memory Killer), OOM的策略更多的是用于分配內(nèi)存不足時(shí)觸發(fā),將得分最高的進(jìn)程殺掉。而lmk則會(huì)每隔一段時(shí)間檢查一次,當(dāng)系統(tǒng)剩余可用內(nèi)存較低時(shí),便會(huì)觸發(fā)殺進(jìn)程的策 略,根據(jù)不同的剩余內(nèi)存檔位來來選擇殺不同優(yōu)先級(jí)的進(jìn)程,而不是等到OOM時(shí)再來殺進(jìn)程,真正OOM時(shí)系統(tǒng)可能已經(jīng)處于異常狀態(tài),系統(tǒng)更希望的是未雨綢 繆,在內(nèi)存很低時(shí)來殺掉一些優(yōu)先級(jí)較低的進(jìn)程來保障后續(xù)操作的順利進(jìn)行。

二. framework層

位于ProcessList.java中定義了3種命令類型,這些文件的定義必須跟lmkd.c定義完全一致,格式分別如下:

LMK_TARGET...(upto6pairs)

LMK_PROCPRIO

LMK_PROCREMOVE

功能

命令

對(duì)應(yīng)方法

觸發(fā)時(shí)機(jī)

更新oom_adjLMK_TARGETupdateOomLevelsAMS.updateConfiguration

設(shè)置進(jìn)程adjLMK_PROCPRIOsetOomAdjAMS.applyOomAdjLocked

移除進(jìn)程LMK_PROCREMOVEremoveAMS.handleAppDiedLocked/cleanUpApplicationRecordLocked

在前面文章Android進(jìn)程調(diào)度之a(chǎn)dj算法中有講到AMS.applyOomAdjLocked,接下來以這個(gè)過程為主線開始分析。

2.1 AMS.applyOomAdjLocked

privatefinalbooleanapplyOomAdjLocked(ProcessRecordapp,booleandoingAll,longnow,

longnowElapsed){

...

if(app.curAdj!=app.setAdj){

//【見小節(jié)2.2】

ProcessList.setOomAdj(app.pid,app.info.uid,app.curAdj);

app.setAdj=app.curAdj;

}

...

}

2.2 PL.setOomAdj

publicstaticfinalvoidsetOomAdj(intpid,intuid,intamt){

//當(dāng)adj=16,則直接返回

if(amt==UNKNOWN_ADJ)

return;

longstart=SystemClock.elapsedRealtime();

ByteBufferbuf=ByteBuffer.allocate(4*4);

buf.putInt(LMK_PROCPRIO);

buf.putInt(pid);

buf.putInt(uid);

buf.putInt(amt);

//將16Byte字節(jié)寫入socket【見小節(jié)2.3】

writeLmkd(buf);

longnow=SystemClock.elapsedRealtime();

if((now-start)>250){

Slog.w("ActivityManager","SLOW OOM ADJ: "+(now-start)+"ms for pid "+pid

+" = "+amt);

}

}

buf大小為16個(gè)字節(jié),依次寫入LMK_PROCPRIO(命令類型), pid(進(jìn)程pid), uid(進(jìn)程uid), amt(目標(biāo)adj),將這些字節(jié)通過socket發(fā)送給lmkd.

2.3 PL.writeLmkd

privatestaticvoidwriteLmkd(ByteBufferbuf){

//當(dāng)socket打開失敗會(huì)嘗試3次

for(inti=0;i<3;i++){

if(sLmkdSocket==null){

//打開socket 【見小節(jié)2.4】

if(openLmkdSocket()==false){

try{

Thread.sleep(1000);

}catch(InterruptedExceptionie){

}

continue;

}

}

try{

//將buf信息寫入lmkd socket

sLmkdOutputStream.write(buf.array(),0,buf.position());

return;

}catch(IOExceptionex){

try{

sLmkdSocket.close();

}catch(IOExceptionex2){

}

sLmkdSocket=null;

}

}

}

當(dāng)sLmkdSocket為空,并且打開失敗,重新執(zhí)行該操作;

當(dāng)sLmkdOutputStream寫入buf信息失敗,則會(huì)關(guān)閉sLmkdSocket,重新執(zhí)行該操作;

這個(gè)重新執(zhí)行操作最多3次,如果3次后還失敗,則writeLmkd操作會(huì)直接結(jié)束。嘗試3次,則不管結(jié)果如何都將退出該操作,可見writeLmkd寫入操作還有可能失敗的。

2.4 PL.openLmkdSocket

privatestaticbooleanopenLmkdSocket(){

try{

sLmkdSocket=newLocalSocket(LocalSocket.SOCKET_SEQPACKET);

//與遠(yuǎn)程lmkd守護(hù)進(jìn)程建立socket連接

sLmkdSocket.connect(

newLocalSocketAddress("lmkd",

LocalSocketAddress.Namespace.RESERVED));

sLmkdOutputStream=sLmkdSocket.getOutputStream();

}catch(IOExceptionex){

Slog.w(TAG,"lowmemorykiller daemon socket open failed");

sLmkdSocket=null;

returnfalse;

}

returntrue;

}

sLmkdSocket 采用的是SOCK_SEQPACKET,這是類型的socket能提供順序確定的,可靠的,雙向基于連接的socket endpoint,與類型SOCK_STREAM很相似,唯一不同的是SEQPACKET保留消息的邊界,而SOCK_STREAM是基于字節(jié)流,并不會(huì) 記錄邊界。

舉例:本地通過write()系統(tǒng)調(diào)用向遠(yuǎn)程先后發(fā)送兩組數(shù)據(jù):一組4字節(jié),一組8字節(jié);對(duì)于 SOCK_SEQPACKET類型通過read()能獲知這是兩組數(shù)據(jù)以及大小,而對(duì)于SOCK_STREAM類型,通過read()一次性讀取到12個(gè) 字節(jié),并不知道數(shù)據(jù)包的邊界情況。

常見的數(shù)據(jù)類型還有SOCK_DGRAM,提供數(shù)據(jù)報(bào)形式,用于udp這樣不可靠的通信過程。

再 回到openLmkdSocket()方法,該方法是打開一個(gè)名為lmkd的socket,類型為 LocalSocket.SOCKET_SEQPACKET,這只是一個(gè)封裝,真實(shí)類型就是SOCK_SEQPACKET。先跟遠(yuǎn)程lmkd守護(hù)進(jìn)程建立 連接,再向其通過write()將數(shù)據(jù)寫入該socket,再接下來進(jìn)入lmkd過程。

三. lmkd

lmkd是由init進(jìn)程,通過解析init.rc文件來啟動(dòng)的lmkd守護(hù)進(jìn)程,lmkd會(huì)創(chuàng)建名為lmkd的socket,節(jié)點(diǎn)位于/dev/socket/lmkd,該socket用于跟上層framework交互。

servicelmkd/system/bin/lmkd

classcore

critical

socket lmkdseqpacket0660system system

writepid/dev/cpuset/system-background/tasks

lmkd啟動(dòng)后,接下里的操作都在platform/system/core/lmkd/lmkd.c文件,首先進(jìn)入main()方法

3.1 main

intmain(intargc__unused,char**argv__unused){

structsched_paramparam={

.sched_priority=1,

};

mlockall(MCL_FUTURE);

sched_setscheduler(0,SCHED_FIFO,?m);

//初始化【見小節(jié)3.2】

if(!init())

mainloop();//成功后進(jìn)入loop [見小節(jié)3.3]

ALOGI("exiting");

return0;

}

3.2 init

staticintinit(void){

structepoll_eventepev;

inti;

intret;

page_k=sysconf(_SC_PAGESIZE);

if(page_k== -1)

page_k=PAGE_SIZE;

page_k/=1024;

//創(chuàng)建epoll監(jiān)聽文件句柄

epollfd=epoll_create(MAX_EPOLL_EVENTS);

//獲取lmkd控制描述符

ctrl_lfd=android_get_control_socket("lmkd");

//監(jiān)聽lmkd socket

ret=listen(ctrl_lfd,1);

epev.events=EPOLLIN;

epev.data.ptr=(void*)ctrl_connect_handler;

//將文件句柄ctrl_lfd,加入epoll句柄

if(epoll_ctl(epollfd,EPOLL_CTL_ADD,ctrl_lfd,&epev)== -1){

return-1;

}

maxevents++;

//該路徑是否具有可寫的權(quán)限

use_inkernel_interface= !access(INKERNEL_MINFREE_PATH,W_OK);

if(use_inkernel_interface){

ALOGI("Using in-kernel low memory killer interface");

}else{

ret=init_mp(MEMPRESSURE_WATCH_LEVEL,(void*)&mp_event);

if(ret)

ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");

}

for(i=0;i<=ADJTOSLOT(OOM_SCORE_ADJ_MAX);i++){

procadjslot_list[i].next= &procadjslot_list[i];

procadjslot_list[i].prev= &procadjslot_list[i];

}

return0;

}

這 里,通過檢驗(yàn)/sys/module/lowmemorykiller/parameters/minfree節(jié)點(diǎn)是否具有可寫權(quán)限來判斷是否使用 kernel接口來管理lmk事件。默認(rèn)該節(jié)點(diǎn)是具有系統(tǒng)可寫的權(quán)限,也就意味著use_inkernel_interface=1.

3.3 mainloop

staticvoidmainloop(void){

while(1){

structepoll_eventevents[maxevents];

intnevents;

inti;

ctrl_dfd_reopened=0;

//等待epollfd上的事件

nevents=epoll_wait(epollfd,events,maxevents,-1);

if(nevents== -1){

if(errno==EINTR)

continue;

continue;

}

for(i=0;i

if(events[i].events&EPOLLERR)

ALOGD("EPOLLERR on event #%d",i);

// 當(dāng)事件到來,則調(diào)用ctrl_connect_handler方法 【見小節(jié)3.4】

if(events[i].data.ptr)

(*(void(*)(uint32_t))events[i].data.ptr)(events[i].events);

}

}

}

主循環(huán)調(diào)用epoll_wait(),等待epollfd上的事件,當(dāng)接收到中斷或者不存在事件,則執(zhí)行continue操作。當(dāng)事件到來,則 調(diào)用的ctrl_connect_handler方法,該方法是由init()過程中設(shè)定的方法。

3.4 ctrl_connect_handler

staticvoidctrl_connect_handler(uint32_t events__unused){

structepoll_eventepev;

if(ctrl_dfd>=0){

ctrl_data_close();

ctrl_dfd_reopened=1;

}

ctrl_dfd=accept(ctrl_lfd,NULL,NULL);

if(ctrl_dfd<0){

ALOGE("lmkd control socket accept failed; errno=%d",errno);

return;

}

ALOGI("ActivityManager connected");

maxevents++;

epev.events=EPOLLIN;

epev.data.ptr=(void*)ctrl_data_handler;

//將ctrl_lfd添加到epollfd

if(epoll_ctl(epollfd,EPOLL_CTL_ADD,ctrl_dfd,&epev)== -1){

ALOGE("epoll_ctl for data connection socket failed; errno=%d",errno);

ctrl_data_close();

return;

}

}

當(dāng)事件觸發(fā),則調(diào)用ctrl_data_handler

3.5 ctrl_data_handler

staticvoidctrl_data_handler(uint32_tevents){

if(events&EPOLLHUP){

//ActivityManager 連接已斷開

if(!ctrl_dfd_reopened)

ctrl_data_close();

}elseif(events&EPOLLIN){

//[見小節(jié)3.6]

ctrl_command_handler();

}

}

3.6 ctrl_command_handler

staticvoidctrl_command_handler(void){

intibuf[CTRL_PACKET_MAX/sizeof(int)];

intlen;

intcmd= -1;

intnargs;

inttargets;

len=ctrl_data_read((char*)ibuf,CTRL_PACKET_MAX);

if(len<=0)

return;

nargs=len/sizeof(int)-1;

if(nargs<0)

gotowronglen;

//將網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換為主機(jī)字節(jié)順序

cmd=ntohl(ibuf[0]);

switch(cmd){

caseLMK_TARGET:

targets=nargs/2;

if(nargs&0x1||targets>(int)ARRAY_SIZE(lowmem_adj))

gotowronglen;

cmd_target(targets,&ibuf[1]);

break;

caseLMK_PROCPRIO:

if(nargs!=3)

gotowronglen;

//設(shè)置進(jìn)程adj【見小節(jié)3.7】

cmd_procprio(ntohl(ibuf[1]),ntohl(ibuf[2]),ntohl(ibuf[3]));

break;

caseLMK_PROCREMOVE:

if(nargs!=1)

gotowronglen;

cmd_procremove(ntohl(ibuf[1]));

break;

default:

ALOGE("Received unknown command code %d",cmd);

return;

}

return;

wronglen:

ALOGE("Wrong control socket read length cmd=%d len=%d",cmd,len);

}

CTRL_PACKET_MAX 大小等于 (sizeof(int) * (MAX_TARGETS * 2 + 1));而MAX_TARGETS=6,對(duì)于sizeof(int)=4的系統(tǒng),則CTRL_PACKET_MAX=52。 獲取framework傳遞過來的buf數(shù)據(jù)后,根據(jù)3種不同的命令,進(jìn)入不同的分支。 接下來,繼續(xù)以前面?zhèn)鬟f過來的LMK_PROCPRIO命令來往下講解,進(jìn)入cmd_procprio過程。

3.7 cmd_procprio

staticvoidcmd_procprio(intpid,intuid,intoomadj){

structproc*procp;

charpath[80];

charval[20];

...

snprintf(path,sizeof(path),"/proc/%d/oom_score_adj",pid);

snprintf(val,sizeof(val),"%d",oomadj);

//向節(jié)點(diǎn)/proc//oom_score_adj寫入oomadj

writefilestring(path,val);

//當(dāng)使用kernel方式則直接返回

if(use_inkernel_interface)

return;

procp=pid_lookup(pid);

if(!procp){

procp=malloc(sizeof(structproc));

if(!procp){

// Oh, the irony.??May need to rebuild our state.

return;

}

procp->pid=pid;

procp->uid=uid;

procp->oomadj=oomadj;

proc_insert(procp);

}else{

proc_unslot(procp);

procp->oomadj=oomadj;

proc_slot(procp);

}

}

向節(jié)點(diǎn)/proc//oom_score_adj寫入oomadj。由于use_inkernel_interface=1,那么再接下里需要看看kernel的情況

3.8 小節(jié)

use_inkernel_interface該值后續(xù)應(yīng)該會(huì)逐漸采用用戶空間策略。不過目前仍為 use_inkernel_interface=1則有:

LMK_PROCPRIO: 向/proc//oom_score_adj寫入oomadj,則直接返回;

LMK_PROCREMOVE:不做任何事,直接返回;

LMK_TARGET:分別向/sys/module/lowmemorykiller/parameters目錄下的minfree和adj節(jié)點(diǎn)寫入相應(yīng)信息;

四. Kernel層

lowmemorykiller driver位于 drivers/staging/Android/lowmemorykiller.c

4.1 lowmemorykiller初始化

staticstructshrinkerlowmem_shrinker={

.scan_objects=lowmem_scan,

.count_objects=lowmem_count,

.seeks=DEFAULT_SEEKS*16

};

staticint__init lowmem_init(void)

{

register_shrinker(&lowmem_shrinker);

return0;

}

staticvoid__exit lowmem_exit(void)

{

unregister_shrinker(&lowmem_shrinker);

}

module_init(lowmem_init);

module_exit(lowmem_exit);

通過register_shrinker和unregister_shrinker分別用于初始化和退出。

4.2 shrinker

LMK驅(qū)動(dòng)通過注冊(cè)shrinker來實(shí)現(xiàn)的,shrinker是linux kernel標(biāo)準(zhǔn)的回收內(nèi)存page的機(jī)制,由內(nèi)核線程kswapd負(fù)責(zé)監(jiān)控。

當(dāng) 內(nèi)存不足時(shí)kswapd線程會(huì)遍歷一張shrinker鏈表,并回調(diào)已注冊(cè)的shrinker函數(shù)來回收內(nèi)存page,kswapd還會(huì)周期性喚醒來執(zhí)行 內(nèi)存操作。每個(gè)zone維護(hù)active_list和inactive_list鏈表,內(nèi)核根據(jù)頁面活動(dòng)狀態(tài)將page在這兩個(gè)鏈表之間移動(dòng),最終通過 shrink_slab和shrink_zone來回收內(nèi)存頁,有興趣想進(jìn)一步了解linux內(nèi)存回收機(jī)制,可自行研究,這里再回到 LowMemoryKiller的過程分析。

4.3 lowmem_count

staticunsignedlonglowmem_count(structshrinker*s,

structshrink_control*sc)

{

returnglobal_page_state(NR_ACTIVE_ANON)+

global_page_state(NR_ACTIVE_FILE)+

global_page_state(NR_INACTIVE_ANON)+

global_page_state(NR_INACTIVE_FILE);

}

ANON代表匿名映射,沒有后備存儲(chǔ)器;FILE代表文件映射; 內(nèi)存計(jì)算公式= 活動(dòng)匿名內(nèi)存 + 活動(dòng)文件內(nèi)存 + 不活動(dòng)匿名內(nèi)存 + 不活動(dòng)文件內(nèi)存

4.4 lowmem_scan

當(dāng)觸發(fā)lmkd,則先殺oom_adj最大的進(jìn)程, 當(dāng)oom_adj相等時(shí),則選擇oom_score_adj最大的進(jìn)程.

staticunsignedlonglowmem_scan(structshrinker*s,structshrink_control*sc)

{

structtask_struct*tsk;

structtask_struct*selected=NULL;

unsignedlongrem=0;

inttasksize;

inti;

shortmin_score_adj=OOM_SCORE_ADJ_MAX+1;

intminfree=0;

intselected_tasksize=0;

shortselected_oom_score_adj;

intarray_size=ARRAY_SIZE(lowmem_adj);

//獲取當(dāng)前剩余內(nèi)存大小

intother_free=global_page_state(NR_FREE_PAGES)-totalreserve_pages;

intother_file=global_page_state(NR_FILE_PAGES)-

global_page_state(NR_SHMEM)-

total_swapcache_pages();

//獲取數(shù)組大小

if(lowmem_adj_size

array_size=lowmem_adj_size;

if(lowmem_minfree_size

array_size=lowmem_minfree_size;

//遍歷lowmem_minfree數(shù)組找出相應(yīng)的最小adj值

for(i=0;i

minfree=lowmem_minfree[i];

if(other_free

min_score_adj=lowmem_adj[i];

break;

}

}

if(min_score_adj==OOM_SCORE_ADJ_MAX+1){

return0;

}

selected_oom_score_adj=min_score_adj;

rcu_read_lock();

for_each_process(tsk){

structtask_struct*p;

shortoom_score_adj;

if(tsk->flags&PF_KTHREAD)

continue;

p=find_lock_task_mm(tsk);

if(!p)

continue;

if(test_tsk_thread_flag(p,TIF_MEMDIE)&&

time_before_eq(jiffies,lowmem_deathpending_timeout)){

task_unlock(p);

rcu_read_unlock();

return0;

}

oom_score_adj=p->signal->oom_score_adj;

//小于目標(biāo)adj的進(jìn)程,則忽略

if(oom_score_adj

task_unlock(p);

continue;

}

//獲取的是進(jìn)程的Resident Set Size,也就是進(jìn)程獨(dú)占內(nèi)存 + 共享庫(kù)大小。

tasksize=get_mm_rss(p->mm);

task_unlock(p);

if(tasksize<=0)

continue;

//算法關(guān)鍵,選擇oom_score_adj最大的進(jìn)程中,并且rss內(nèi)存最大的進(jìn)程.

if(selected){

if(oom_score_adj

continue;

if(oom_score_adj==selected_oom_score_adj&&

tasksize<=selected_tasksize)

continue;

}

selected=p;

selected_tasksize=tasksize;

selected_oom_score_adj=oom_score_adj;

lowmem_print(2,"select '%s' (%d), adj %hd, size %d, to kill\n",

p->comm,p->pid,oom_score_adj,tasksize);

}

if(selected){

longcache_size=other_file*(long)(PAGE_SIZE/1024);

longcache_limit=minfree*(long)(PAGE_SIZE/1024);

longfree=other_free*(long)(PAGE_SIZE/1024);

lowmem_deathpending_timeout=jiffies+HZ;

set_tsk_thread_flag(selected,TIF_MEMDIE);

//向選中的目標(biāo)進(jìn)程發(fā)送signal 9來殺掉目標(biāo)進(jìn)程

send_sig(SIGKILL,selected,0);

rem+=selected_tasksize;

}

rcu_read_unlock();

returnrem;

}

選擇oom_score_adj最大的進(jìn)程中,并且rss內(nèi)存最大的進(jìn)程作為選中要?dú)⒌倪M(jìn)程。

殺進(jìn)程方式:send_sig(SIGKILL, selected, 0)向選中的目標(biāo)進(jìn)程發(fā)送signal 9來殺掉目標(biāo)進(jìn)程。

另外,lowmem_minfree[]和lowmem_adj[]數(shù)組大小個(gè)數(shù)為6,通過如下兩條命令:

module_param_named(debug_level,lowmem_debug_level,uint,S_IRUGO|S_IWUSR);

module_param_array_named(adj,lowmem_adj,short,&lowmem_adj_size,S_IRUGO|S_IWUSR);

當(dāng)如下節(jié)點(diǎn)數(shù)據(jù)發(fā)送變化時(shí),會(huì)通過修改lowmem_minfree[]和lowmem_adj[]數(shù)組:

/sys/module/lowmemorykiller/parameters/minfree

/sys/module/lowmemorykiller/parameters/adj

五、總結(jié)

本 文主要從frameworks的ProcessList.java調(diào)整adj,通過socket通信將事件發(fā)送給native的守護(hù)進(jìn)程 lmkd;lmkd再根據(jù)具體的命令來執(zhí)行相應(yīng)操作,其主要功能 更新進(jìn)程的oom_score_adj值以及l(fā)owmemorykiller驅(qū)動(dòng)的parameters(包括minfree和adj);

最 后講到了lowmemorykiller驅(qū)動(dòng),通過注冊(cè)shrinker,借助linux標(biāo)準(zhǔn)的內(nèi)存回收機(jī)制,根據(jù)當(dāng)前系統(tǒng)可用內(nèi)存以及 parameters配置參數(shù)(adj,minfree)來選取合適的selected_oom_score_adj,再?gòu)乃羞M(jìn)程中選擇adj大于該目 標(biāo)值的并且占用rss內(nèi)存最大的進(jìn)程,將其殺掉,從而釋放出內(nèi)存。

5.1 lmkd參數(shù):

oom_adj:代表進(jìn)程的優(yōu)先級(jí), 數(shù)值越大,優(yōu)先級(jí)越低,越容易被殺. 取值范圍[-16, 15]

oom_score_adj: 取值范圍[-1000, 1000]

oom_score:lmk策略中貌似并沒有看到使用的地方,這個(gè)應(yīng)該是oom才會(huì)使用。

想查看某個(gè)進(jìn)程的上述3值,只需要知道pid,查看以下幾個(gè)節(jié)點(diǎn):

/proc//oom_adj

/proc//oom_score_adj

/proc//oom_score

對(duì)于oom_adj與oom_score_adj有一定的映射關(guān)系:

當(dāng)oom_adj = 15, 則oom_score_adj=1000;

當(dāng)oom_adj < 15, 則oom_score_adj= oom_adj * 1000/17;

5.2 driver參數(shù)

/sys/module/lowmemorykiller/parameters/minfree(代表page個(gè)數(shù))

/sys/module/lowmemorykiller/parameters/adj(代表oom_score_adj)

例 如:將1,6寫入節(jié)點(diǎn)/sys/module/lowmemorykiller/parameters/adj,將1024,8192寫入節(jié)點(diǎn)/sys /module/lowmemorykiller/parameters/minfree。策略:當(dāng)系統(tǒng)可用內(nèi)存低于8192個(gè)pages時(shí),則會(huì)殺掉 oom_score_adj>=6的進(jìn)程;當(dāng)系統(tǒng)可用內(nèi)存低于1024個(gè)pages時(shí),則會(huì)殺掉oom_score_adj>=1的進(jìn)程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,402評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,596評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,027評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評(píng)論 1 280
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,398評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,743評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容