关于OOM和内存回收的一点浅显的认识
来源:互联网 发布:rbac权限管理系统源码 编辑:程序博客网 时间:2024/04/29 00:12
一、LowMemory Killer
Low Memory Killer在用户空间中指定了一组内存临界值,当其中的某个值与进程描述中的oom_adj值在同一范围时,该进程将被Kill掉。通常,在
/sys/module/lowmemorykiller/parameters/adj中指定oom_adj的最小值,在
/sys/module/lowmemorykiller/parameters/minfree中储存空闲页面的数量,所有的值都用一个逗号将其隔开且以升序排列。
1、Android单个进程内存分配策略
系统会给每一个进程分配内存,不同的Android设备有可能不一样,一般情况这个值在system/build.prop中可以查看,设定可以在system.prop中设定属性dalvik.vm.heapsize和dalvik.vm.heapgrowthlimit的值确定,Android L之后一般将值设定为:
dalvik.vm. heapgrowthlimit=128m
dalvik.vm.heapsize=256m
dalvik.vm. heapgrowthlimit是一般情况下分配给APP的内存大小,dvmheap是可增长的,是给受控制的应用分配的,这些应用dvm heap size超过这个值会出现OOM的情况;在Android开发中,如果要使用dalvik.vm. heapsize ,需要在manifest中指定android:largeHeap为true,这样dvm heap最大可达dalvik.vm.heapsize,主要用在一些大型游戏。
2、lowmem_adj和lowmem_minfree
在kernel/drivers/staging/android/路径下的lowmemorykiller.c文件定义了lowmem_adj和lowmem_minfree两个数组,一个是adj 数组,描述process 所对应的oom_adj,另外一个是minfree数组,描述process 所对应的memory 的阈值。如下:
static int lowmem_adj[6]={
0,
1,
6,
12,
};
static int lowmem_adj_size = 4;
static size_t lowmem_minfree[6]={ //其中的元素单位是Page,1page=4KB
3*512,// 6MB
2*1024,// 8MB
4*1024,// 16MB
16*1024,// 64MB
};
static int lowmem_minfree_size =4;
可以看出,当一个进程的空闲空间剩余为3*512个页面时,这时对应的lowmem_adj数组中的元素是0,所以表示oom_adj值为0或者更大的进程会被Kill掉;当一个进程的空闲空间下降到2´1024个页面时,oom_adj值为1或者更大的进程会被Kill掉,依此类推。其实更简明的理解就是满足以下条件的进程将被优先Kill掉:
可杀进程:优先级最低 + 内存占用最多
进程描述符中的signal_struct->oom_adj表示当内存短缺时进程被选择并Kill的优先级,取值范围是-17~15。如果是-17,则表示不会被选中,值越大越可能被选中。当某个进程被选中后,内核会发送SIGKILL信号将其Kill掉。
3、Low Memory Killer的具体实现
在了解了Low Memory Killer的原理之后,我们再来看如何实现这个驱动。Low Memory Killer驱动的实现位于kernel/drivers/misc/lowmemorykiller.c。该驱动的实现非常简单,其初始化与退出操作也是我们到目前为止见过的最简单的,代码如下:
static int __init lowmem_init(void)
{
register_shrinker(&lowmem_shrinker);
return 0;
}
static void __exit lowmem_exit(void)
{
unregister_shrinker(&lowmem_shrinker);
}
module_init(lowmem_init);
module_exit(lowmem_exit);
在初始化函数lowmem_init中通过register_shrinker注册了一个shrinker为lowmem_shrinker,退出时又调用了函数lowmem_exit,通过unregister_shrinker来卸载被注册的lowmem_shrinker。其中lowmem_shrinker的定义如下:
static struct shrinker lowmem_shrinker={
.shrink = lowmem_shrink,
.seeks = DEFAULT_SEEKS *16
};
lowmem_shrink是这个驱动的核心实现,当内存不足时就会调用lowmem_shrink方法来Kill掉某些进程。下面来分析其具体实现,实现代码如下:
static int lowmem_shrink(struct shrinker*s, struct shrink_control*sc)
{
struct task_struct *tsk;
struct task_struct *selected= NULL;
int rem =0;
int tasksize;
int i;
short min_score_adj = OOM_SCORE_ADJ_MAX +1;
int minfree =0;
int selected_tasksize = 0;
short selected_oom_score_adj;
int array_size = ARRAY_SIZE(lowmem_adj);
int other_free = global_page_state(NR_FREE_PAGES)- totalreserve_pages;
int other_file = global_page_state(NR_FILE_PAGES)-
global_page_state(NR_SHMEM);
..............................
lowmem_print(4,"lowmem_shrink %lu, %x, return %d\n", sc->nr_to_scan, sc->gfp_mask, rem);
rcu_read_unlock();
spin_unlock(&lowmem_shrink_lock);
return rem;
}
首先确定我们所定义的lowmem_adj和lowmem_minfree数组的大小(元素个数)是否一致,如果不一致则以最小的为基准,因为我们需要通过比较lowmem_minfree中的空闲储存空间的值,以确定最小min_adj值(当满足其条件时,通过其数组索引来寻找lowmem_adj中对应元素的值):
if (lowmem_adj_size < array_size)
array_size = lowmem_adj_size;
if (lowmem_minfree_size < array_size)
array_size = lowmem_minfree_size;
for (i = 0; i < array_size; i++){
minfree = lowmem_minfree[i];
if (other_free < minfree&& other_file< minfree){
min_score_adj = lowmem_adj[i];
break;
}
}
其次检测min_adj的值是否是初始值“OOM_ADJUST_MAX + 1”,如果是,则表示没有满足条件的min_adj值,否则进入下一步;然后使用循环 for_each_process(p)对每一个进程块进行判断,通过min_adj来寻找满足条件的具体进程(主要包括对oomkilladj和task_struct进行判断):
if (sc->nr_to_scan <= 0 || min_score_adj == OOM_SCORE_ADJ_MAX+1){
lowmem_print(5,"lowmem_shrink %lu, %x, return %d\n",
sc->nr_to_scan, sc->gfp_mask, rem);
spin_unlock(&lowmem_shrink_lock);
return rem;
}
最后,对找到的进程进行NULL判断,通过“send_sig(SIGKILL, selected, 0);”发送一条SIGKILL信号到内核,Kill掉被选中的“selected”进程。
可以看出,其中多处用到了global_page_state函数。有很多人找不到这个函数,其实它被定义在了kernel/include/linux/vmstat.h中,其参数使用zone_stat_item枚举,被定义在kernel/include/linux/mmzone.h中。
二、上层实现
Android的每一个应用都是运行在一个独立的DVM中,他们之间互不影响;应用退出之后,并没有立马杀死进程,进程依然停留在内存中,这么做的目的是为了提高下次启动时的速度。而在Android中管理进程的模块是AMS,主要有LRU weight、OOM adj、Low MemoryKiller共同来完成进程的管理。
在ActivityManagerService.java初始化了对象ProcessList.java。对进程的oom_adj的处理都是在ProcessList中进行的。
1、oom_adj和oom_score_adj
AMS 启动后,将根据屏幕分辨率以及内存大小调整默认的LMK 的阈值,对应的具体的代码在:
frameworks/base/services/java/com/android/server/am/ProcessList.java
可以修正方法updateOomLevel来调整LMK 的oom_adj和oom_score_adj这两个参数,具体代码如下:
ProcessList() {
MemInfoReader minfo =new MemInfoReader();
minfo.readMemInfo();
mTotalMemMb = minfo.getTotalSize()/(1024*1024);
updateOomLevels(0,0,false);
}
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 =320*480; // 153600
int maxSize =1280*800;// 1024000 230400 870400 .264
float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
//Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth + " dh=" + displayHeight);
StringBuilder adjString =new StringBuilder();
StringBuilder memString =new StringBuilder();
float scale = scaleMem> scaleDisp? scaleMem: scaleDisp;
if (scale < 0) scale =0;
else if (scale >1) scale=1;
for (int i=0; i<mOomAdj.length; i++) {
long low = mOomMinFreeLow[i];
long high = mOomMinFreeHigh[i];
mOomMinFree[i]=(long)(low+((high-low)*scale));
if (i > 0) {
adjString.append(',');
memString.append(',');
}
adjString.append(mOomAdj[i]);
memString.append((mOomMinFree[i]*1024)/PAGE_SIZE);
}
//Slog.i("XXXXXXX", "******************************* MINFREE: " + memString);
if (write) {
writeFile("/sys/module/lowmemorykiller/parameters/adj", adjString.toString());
writeFile("/sys/module/lowmemorykiller/parameters/minfree", memString.toString());
}
// GB: 2048,3072,4096,6144,7168,8192
// HC: 8192,10240,12288,14336,16384,20480
}
2、进程的oom_adj值来源
1) init.rc中,init进程的pid为1,omm_adj被配置为-16,永远不会被杀死。
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj-16
start ueventd
2) 通过Process.setOomAdj(int pid, int amt)进行设置,其中在ActivityManagerService中applyOomAdjLocked()函数中就有调用。
public static final native boolean setOomAdj(int pid,int amt);
android_util_Process.cpp
jboolean android_os_Process_setOomAdj(JNIEnv* env, jobject clazz,
jint pid, jint adj)
{
#ifdef HAVE_OOM_ADJ
char text[64];
sprintf(text,"/proc/%d/oom_adj", pid);
int fd = open(text, O_WRONLY);
if (fd >= 0) {
sprintf(text,"%d", adj);
write(fd, text, strlen(text));
close(fd);
}
return true;
#endif
return false;
}
3、ActivityManageService.java中的OOM
在AMS中主要是在进程数达到阈值ProcessList.CACHED_APP_MAX_ADJ时对kill进程的选择过程,进程的oom_adj也会做相应的调整,下面是函数updateOomAdjLocked:
final int emptyProcessLimit;
final int cachedProcessLimit;
if (mProcessLimit <=0){
emptyProcessLimit = cachedProcessLimit=0;
} else if (mProcessLimit==1){
emptyProcessLimit =1;
cachedProcessLimit =0;
} else {
emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit);
cachedProcessLimit = mProcessLimit- emptyProcessLimit;
}
// Let's determine how many processes we have running vs.
// how many slots we have for background processes; we may want
// to put multiple processes in a slot of there are enough of
// them.
int numSlots =(ProcessList.CACHED_APP_MAX_ADJ
- ProcessList.CACHED_APP_MIN_ADJ+1)/2;
int numEmptyProcs = N - mNumNonCachedProcs- mNumCachedHiddenProcs;
if (numEmptyProcs > cachedProcessLimit){
// If there are more empty processes than our limit on cached
// processes, then use the cached process limit for the factor.
// This ensures that the really old empty processes get pushed
// down to the bottom, so if we are running low on memory we will
// have a better chance at keeping around more cached processes
// instead of a gazillion empty processes.
numEmptyProcs = cachedProcessLimit;
}
在上面,emptyProcessLimit的值是ProcessList.MAX_CACHED_APPS的2/3,这里只是对一些空进程的操作,N = mLruProcesses.size()是最少使用进程的数目;来看下在AMS中的updateLruProcessLocked方法:
final void updateLruProcessLocked(ProcessRecord app,boolean activityChange,
ProcessRecord client){
final boolean hasActivity = app.activities.size()>0 || app.hasClientActivities;
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity){
// The process has activties, so we are only going to allow activity-based
// adjustments move it. It should be kept in the front of the list with other
// processes that have activities, and we don't want those to change their
// order except due to activity operations.
return;
}
..............................................
//分为三种情况:activity,service,其他
int nextIndex;
if (hasActivity) {
final int N = mLruProcesses.size();
if (app.activities.size()==0 && mLruProcessActivityStart<(N-1)){
// Process doesn't have activities, but has clients with
// activities... move it up, but one below the top (the top
// should always have a real activity).
if (DEBUG_LRU) Slog.d(TAG,"Adding to second-top of LRU activity list: " + app);
mLruProcesses.add(N-1, app);
// To keep it from spamming the LRU list (by making a bunch of clients),
// we will push down any other entries owned by the app.
final int uid = app.info.uid;
for (int i=N-2; i>mLruProcessActivityStart; i--) {
ProcessRecord subProc = mLruProcesses.get(i);
if (subProc.info.uid== uid){
// We want to push this one down the list. If the process after
// it is for the same uid, however, don't do so, because we don't
// want them internally to be re-ordered.
if(mLruProcesses.get(i-1).info.uid!= uid){
if(DEBUG_LRU) Slog.d(TAG,"Pushing uid " + uid+" swapping at "+ i
+": "+ mLruProcesses.get(i)+" : " + mLruProcesses.get(i-1));
ProcessRecord tmp = mLruProcesses.get(i);
mLruProcesses.set(i, mLruProcesses.get(i-1));
mLruProcesses.set(i-1, tmp);
i--;
}
} else {
// A gap, we can stop here.
break;
}
}
} else {
// Process has activities, put it at the very tipsy-top.
if (DEBUG_LRU) Slog.d(TAG,"Adding to top of LRU activity list: " + app);
mLruProcesses.add(app);
}
nextIndex = mLruProcessServiceStart;
} else if (hasService){
// Process has services, put it at the top of the service list.
if (DEBUG_LRU) Slog.d(TAG,"Adding to top of LRU service list: " + app);
mLruProcesses.add(mLruProcessActivityStart, app);
nextIndex = mLruProcessServiceStart;
mLruProcessActivityStart++;
} else {
// Process not otherwise of interest, it goes to the top of the non-service area.
int index = mLruProcessServiceStart;
if (client != null) {
// If there is a client, don't allow the process to be moved up higher
// in the list than that client.
int clientIndex= mLruProcesses.lastIndexOf(client);
if (DEBUG_LRU && clientIndex<0) Slog.d(TAG,"Unknown client " + client
+" when updating "+ app);
if (clientIndex <= lrui){
// Don't allow the client index restriction to push it down farther in the
// list than it already is.
clientIndex = lrui;
}
if (clientIndex >= 0 && index > clientIndex){
index = clientIndex;
}
}
if (DEBUG_LRU) Slog.d(TAG,"Adding at " + index+" of LRU list: "+ app);
mLruProcesses.add(index, app); //确定APP在mLruProcesses的位置
nextIndex = index-1;
mLruProcessActivityStart++;
mLruProcessServiceStart++;
}
//如果当前应用使用了ContentProvider或是Service,重新计算
// If the app is currently using a content provider or service,
// bump those processes as well.
for (int j=app.connections.size()-1; j>=0; j--){
ConnectionRecord cr = app.connections.valueAt(j);
if (cr.binding != null && !cr.serviceDead&& cr.binding.service!=null
&& cr.binding.service.app!=null
&& cr.binding.service.app.lruSeq!= mLruSeq
&& !cr.binding.service.app.persistent){
nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex,
"service connection", cr, app);
}
}
for (int j=app.conProviders.size()-1; j>=0; j--){
ContentProviderRecord cpr = app.conProviders.get(j).provider;
if (cpr.proc != null && cpr.proc.lruSeq!= mLruSeq&&!cpr.proc.persistent){
nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex,
"provider reference", cpr, app);
}
}
}
LRUweight LRU(最近最少使用)weight主要用来衡量LRU的权重,在android进程启动之后,会以ProcessRecord类型的方式创建一个实例,保存到AMS的mLruProcesses变量中,mLurProcesses会以LRU的顺序来存储进程信息。当有一下情况时会更新mLruProcesses: 1.应用程序异常退出;2.调用AMS杀死进程;3.启动和调度四大组件。
updateLruProcessLocked有两个作用:
Ø 计算进程的LRU序列号和LRU weight;
Ø 在1的基础上,确定在mLruProcesses中的位置;
再来看下updateOomAdjLocked函数,这个函数经过了多次重载:
private final boolean updateOomAdjLocked(ProcessRecord app,int cachedAdj,
ProcessRecord TOP_APP,boolean doingAll,boolean reportingProcessState,long now){
if (app.thread == null){
return false;
}
final boolean wasKeeping = app.keeping;
//computeOomAdjLocked会重新计算进程的oom_adj的值
computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll,
reportingProcessState, now);
}
final void updateOomAdjLocked() {
.............
for (int i=N-1; i>=0; i--) {
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm&& app.thread!=null){
app.procStateChanged=false;
final boolean wasKeeping = app.keeping;
//计算oom_adj
computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
// If we haven't yet assigned the final cached adj
// to the process, do that now.
if (app.curAdj >= ProcessList.UNKNOWN_ADJ){
switch(app.curProcState){
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
。。。。。。
default:
。。。。。。
break;
}
}
//调用applyOomAdjLocked,继而调用setOomAdj和setProcessGroup
applyOomAdjLocked(app, wasKeeping, TOP_APP,true,false, now);
// Count the number of process types.
switch (app.curProcState){
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
mNumCachedHiddenProcs++;
numCached++;
if(numCached> cachedProcessLimit){
//kill掉不需要的进程
killUnneededProcessLocked(app,"cached #"+ numCached);
}
break;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
if(numEmpty> ProcessList.TRIM_EMPTY_APPS
&& app.lastActivityTime< oldTime){
//kill掉不需要的进程
killUnneededProcessLocked(app,"empty for "
+((oldTime+ ProcessList.MAX_EMPTY_TIME- app.lastActivityTime)
/1000)+ "s");
}else{
numEmpty++;
if(numEmpty> emptyProcessLimit){
//kill掉不需要的进程
killUnneededProcessLocked(app,"empty #"+ numEmpty);
}
}
break;
default:
mNumNonCachedProcs++;
break;
}
if (app.isolated && app.services.size()<=0){
// If this is an isolated process, and there are no
// services running in it, then the process is no longer
// needed. We agressively kill these because we can by
// definition not re-use the same process again, and it is
// good to avoid having whatever code was running in them
// left sitting around after no longer needed.
//kill掉不需要的进程
killUnneededProcessLocked(app,"isolated not needed");
}
if (app.curProcState>= ActivityManager.PROCESS_STATE_HOME
&&!app.killedByAm){
numTrimming++;
}
}
}
mNumServiceProcs = mNewNumServiceProcs;
}
其实在updateOomAdjLocked主要做了三个操作:
Ø 调用computeOomAdjLocked方法计算进程的oom_adj的值;
Ø 调用setOomAdj来修改进程的oom adj的值;
Ø 调用killUnneededProcessLocked来kill掉不适用的进程;
updateOomAdjLocked在后面还做了一些对内存的处理优化操作,这里就不介绍了;这里有一个疑问,底层和上层都有kill进程的过程,其中有什么区别呢,我的想法是这样的,底层的kill主要发生在进程出现Low Memory的时候才去做,而上层在AMS中是系统在检测到内存不足时清理缓存的一个重要过程。
三、实用举例
避免后台音乐或是其他应用被lowmemory清理掉:
解决方案:
1、在ActivityManagerService.java中添加如下两个变量:
static final String[] mThirdPartyAppWhiteList={"android.process.media"};
static final int [] mThirdPartyAppAdj={7};
2、在updateOomAdjLocked做这样的修改:
private final boolean updateOomAdjLocked(ProcessRecord app,int cachedAdj,
ProcessRecord TOP_APP,boolean doingAll,boolean reportingProcessState,long now){
if (app.thread == null){
return false;
}
final boolean wasKeeping = app.keeping;
computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
//Add by Jack-Yu
boolean isThirdPartyAppWhiteProcess= false;
int mThirdPartyAdj = ProcessList.HIDDEN_APP_MIN_ADJ;
if (mThirdPartyAppWhiteList.length!= mThirdPartyAppAdj.length)
{
throw new Exception("mThirdPartyAppWhiteList is not match mThirdPartyAppAdj");
}
for(int num=0; num<= mThirdPartyAppWhiteList.length-1;num++)
{
if(mThirdPartyAppWhiteList[num].equals(app.processName)&&
app.curAdj> mThirdPartyAppAdj[num])
{
isThirdPartyAppWhiteProcess =true;
mThirdPartyAdj = mThirdPartyAppAdj[num];
}
}
if(isThirdPartyAppWhiteProcess){
if (Process.setOomAdj(app.pid, mThirdPartyAdj)){
if (DEBUG_SWITCH || DEBUG_OOM_ADJ){
Slog.v(TAG,"Set "+ app.pid+" "+ app.processName+
" adj " + mThirdPartyAdj +": "+ app.adjType);
}
app.setAdj= mThirdPartyAdj;
} else {
Slog.w(TAG,"Failed setting oom adj of "+ app+" to "+ mThirdPartyAdj);
return false
}
return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll,
reportingProcessState, now);
}
上面代码没有经过编译,但大体的代码逻辑还是写清楚了,就设置一个白名单,将APP的oom_adj值设置为7,是PREVIOUS_APP_ADJ,表征前一个进程,这样oom_adj将达到非当前不可见APP的最小值,最后在返回的时候返回false即可。
- 关于OOM和内存回收的一点浅显的认识
- 查找和排序的一点浅显认识
- 关于WebService的浅显认识
- 关于OGRE四元数和旋转的一些浅显认识`
- javaWeb笔记(四) 面向接口编程 一点 浅显的认识
- jamvm的浅显认识
- 关于Editor和Renderer的一点认识
- 关于Android 中 Bitmap 内存回收的一点心得
- 关于COM的一些浅显的认识(读书笔记)
- 对于内存的一点认识
- 关于map和set的浅显理解
- 关于Session的一点认识
- 关于AIR的一点认识
- 关于炒股的一点认识
- 关于框架的一点认识
- 关于学习的一点认识
- 关于HandlerThread的一点认识
- 关于堆栈的一点认识
- java中的语句
- photoView中的fling事件分析
- jdk,tomcat,maven,eclipse安装,配置,集成
- 外媒:指纹识别狂潮来袭 未来将一统智能手机天下
- 替换字符串中的子串
- 关于OOM和内存回收的一点浅显的认识
- 智能机出货量首现下滑 三星遭遇滑铁卢?
- OpenStack快速入门
- “让前任后悔”是怎样一种体验?
- flask web开发:主程序从单文件到多文件的转化——第四步
- 研究表明:睡眠不好跟智能手机没什么关系
- Android 对话框(Dialog)大全 建立自己最个性的对话框
- FPGA FIFO REFERENCE
- web 是 如何工作的(2015/10/22)