关于Android系统的内存回收机制,相信大家都不陌生,Android基于各个应用进程承载四大组件的状态对应用进程进行重要性评估,并在系统内存紧张时根据重要性由低到高来选择杀死应用进程,以达到释放内存的目的。重要性评估由AMS执行,具体来说就是AMS.updateOomAdjLocked函数,反过来说,AMS.updateOomAdjLocked的作用就是更新应用进程的重要性。
应用进程(ProcessRecord)的重要性由三个状态值表示:
- adj:LMK杀进程的评分依据
- procState:指示进程状态
- schedGroup:指示进程调度策略
本文不会分析该函数的具体执行,而是讨论这三个状态值之间的差异,代码参考Android N。
基本的思考
既然要评估进程的重要性,并以此作为LMK回收进程的依据,理论上来讲由单个状态来指示重要性,并告知LMK应该是最好的方案,简单粗暴,逻辑清晰。大家从很多地方都可以得知,oom_adj就是提供给LMK进行内存回收的依据,但是对于procState和schedGroup要么一笔带过,要么干脆不提及。但是既然存在这么两个东西,必然有其存在的意义。
我们先大概讲下AMS.updateOomAdjLocked的执行流程:
updateOomAdjLocked(ProcessRecord app)
-updateOomAdjLocked(ProcessRecord app, int cachedAdj,
ProcessRecord TOP_APP, boolean doingAll, long now)
-computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
boolean doingAll, long now)
-applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
long nowElapsed)
updateOomAdjLocked()
-computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
boolean doingAll, long now)
-applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
long nowElapsed)
有上述两种执行路径,相同的空格缩进表示函数调用栈的同一级,注意updateOomAdjLocked有多个重载版本。重点说明出下面几点:
- updateOomAdjLocked在应用进程的组件运行状态发生改变时被调用,比如有Service启动,有广播接收者收到广播,有Activity启动等,这很好理解,因为进程重要性的计算就依赖于组件运行状态,既然组件运行状态发生了改变,就应该实时更新;
- computeOomAdjLocked根据一定规则计算出三个状态值,这个规则跟Android将进程划分的5个优先级有关系,即前台进程、可见进程、服务进程、后台进程、空进程,这里不详细说明;
- applyOomAdjLocked将computeOomAdjLocked计算出的三个状态值应用起来,即真正发挥这三个状态值的作用。
既然要分析这三个状态值的作用,看源码如何使用这些值就是最好的办法,从上面可以知道,applyOomAdjLocked是我们的切入点。
applyOomAdjLocked
下面代码中涉及的ProcessRecord中cur开头的字段均为computeOomAdjLocked函数计算出的当前状态值,比如curAdj,curProcState,curSchedGroup,下面从代码的角度分别描述它们的作用。
adj
if (app.curAdj != app.setAdj) {
ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": "
+ app.adjType);
app.setAdj = app.curAdj;
app.verifiedAdj = ProcessList.INVALID_ADJ;
}
curAdj是computeOomAdjLocked计算出的adj值,赋值给setAdj,并且调用ProcessList.setOomAdj,继续往下看:
public static final void setOomAdj(int pid, int uid, int amt) {
if (amt == UNKNOWN_ADJ)
return;
long start = SystemClock.elapsedRealtime();
ByteBuffer buf = ByteBuffer.allocate(4 * 4);
buf.putInt(LMK_PROCPRIO);
buf.putInt(pid);
buf.putInt(uid);
buf.putInt(amt);
writeLmkd(buf);
long now = SystemClock.elapsedRealtime();
if ((now-start) > 250) {
Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid
+ " = " + amt);
}
}
private static void writeLmkd(ByteBuffer buf) {
for (int i = 0; i < 3; i++) {
if (sLmkdSocket == null) {
if (openLmkdSocket() == false) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
}
continue;
}
}
try {
sLmkdOutputStream.write(buf.array(), 0, buf.position());
return;
} catch (IOException ex) {
Slog.w(TAG, "Error writing to lowmemorykiller socket");
try {
sLmkdSocket.close();
} catch (IOException ex2) {
}
sLmkdSocket = null;
}
}
}
private static boolean openLmkdSocket() {
try {
sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
sLmkdSocket.connect(
new LocalSocketAddress("lmkd",
LocalSocketAddress.Namespace.RESERVED));
sLmkdOutputStream = sLmkdSocket.getOutputStream();
} catch (IOException ex) {
Slog.w(TAG, "lowmemorykiller daemon socket open failed");
sLmkdSocket = null;
return false;
}
return true;
}
最终是往名为”lmkd”的Socket发送数据,数据内容为pid、uid及对应的adj值,”lmkd”由lmkd进程创建,所以后面的事情就由lmkd来处理。简单地说,lmkd根据各个进程的adj值以及预先设定的内存阈值,根据一定的策略杀进程来释放内存,这里不关心具体的实现,只关心lmkd利用adj值做了哪些事情。也就是说,lmkd的策略仅依赖于adj值,跟procState和schedGroup无关。
procState
if (app.repProcState != app.curProcState) {
app.repProcState = app.curProcState;
changes |= ProcessChangeItem.CHANGE_PROCESS_STATE;
if (app.thread != null) {
try {
if (false) {
//RuntimeException h = new RuntimeException("here");
Slog.i(TAG, "Sending new process state " + app.repProcState
+ " to " + app /*, h*/);
}
app.thread.setProcessState(app.repProcState);
} catch (RemoteException e) {
}
}
}
调用了应用进程ApplicationThread的setProcessState方法:
public void setProcessState(int state) {
updateProcessState(state, true);
}
public void updateProcessState(int processState, boolean fromIpc) {
synchronized (this) {
if (mLastProcessState != processState) {
mLastProcessState = processState;
// Update Dalvik state based on ActivityManager.PROCESS_STATE_* constants.
final int DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
final int DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
int dalvikProcessState = DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE;
// TODO: Tune this since things like gmail sync are important background but not jank perceptible.
if (processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
dalvikProcessState = DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE;
}
VMRuntime.getRuntime().updateProcessState(dalvikProcessState);
if (false) {
Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
+ (fromIpc ? " (from ipc": ""));
}
}
}
}
最后调用了VMRuntime.getRuntime().updateProcessState(dalvikProcessState),传进来的参数processState根据是否小于ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND被转换成了DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE以及DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE。实际上这是告诉ART运行时当前进程状态是可感知还是不可感知,用来切换虚拟机的GC算法,即前台GC或是后台GC。前台GC效率较高但会产生碎片,后台GC效率较低但不存在碎片问题,刚好适应应用进程处于前后台的两种情形。具体可参考:
https://blog.csdn.net/luoshengyang/article/details/45301715
schedGroup
if (app.setSchedGroup != app.curSchedGroup) {
int oldSchedGroup = app.setSchedGroup;
app.setSchedGroup = app.curSchedGroup;
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Setting sched group of " + app.processName
+ " to " + app.curSchedGroup);
if (app.waitingToKill != null && app.curReceiver == null
&& app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
app.kill(app.waitingToKill, true);
success = false;
} else {
int processGroup;
switch (app.curSchedGroup) {
case ProcessList.SCHED_GROUP_BACKGROUND:
processGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
break;
case ProcessList.SCHED_GROUP_DEFAULT:
processGroup = Process.THREAD_GROUP_DEFAULT;
break;
case ProcessList.SCHED_GROUP_SYSTEM:
processGroup = Process.THREAD_GROUP_SYSTEM;
break;
case ProcessList.SCHED_GROUP_AUDIO_APP:
processGroup = Process.THREAD_GROUP_AUDIO_APP;
break;
case ProcessList.SCHED_GROUP_AUDIO_SYS:
processGroup = Process.THREAD_GROUP_AUDIO_SYS;
break;
case ProcessList.SCHED_GROUP_TOP_APP:
processGroup = Process.THREAD_GROUP_TOP_APP;
break;
default:
processGroup = Process.THREAD_GROUP_DEFAULT;
break;
}
long oldId = Binder.clearCallingIdentity();
try {
Process.setProcessGroup(app.pid, processGroup); // 设置进程调度策略
if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
// do nothing if we already switched to RT
if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
// Switch VR thread for app to SCHED_FIFO
if (mInVrMode && app.vrThreadTid != 0) {
try {
Process.setThreadScheduler(app.vrThreadTid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
} catch (IllegalArgumentException e) {
// thread died, ignore
}
}
if (mUseFifoUiScheduling) {
// Switch UI pipeline for app to SCHED_FIFO
app.savedPriority = Process.getThreadPriority(app.pid);
try {
Process.setThreadScheduler(app.pid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
} catch (IllegalArgumentException e) {
// thread died, ignore
}
if (app.renderThreadTid != 0) {
try {
Process.setThreadScheduler(app.renderThreadTid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
} catch (IllegalArgumentException e) {
// thread died, ignore
}
if (DEBUG_OOM_ADJ) {
Slog.d("UI_FIFO", "Set RenderThread (TID " +
app.renderThreadTid + ") to FIFO");
}
} else {
if (DEBUG_OOM_ADJ) {
Slog.d("UI_FIFO", "Not setting RenderThread TID");
}
}
} else { // 提高前台UI相关线程优先级为-10
// Boost priority for top app UI and render threads
Process.setThreadPriority(app.pid, -10);
if (app.renderThreadTid != 0) {
try {
Process.setThreadPriority(app.renderThreadTid, -10);
} catch (IllegalArgumentException e) {
// thread died, ignore
}
}
}
}
} else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
app.curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
// Reset VR thread to SCHED_OTHER
// Safe to do even if we're not in VR mode
if (app.vrThreadTid != 0) {
Process.setThreadScheduler(app.vrThreadTid, Process.SCHED_OTHER, 0);
}
if (mUseFifoUiScheduling) {
// Reset UI pipeline to SCHED_OTHER
Process.setThreadScheduler(app.pid, Process.SCHED_OTHER, 0);
Process.setThreadPriority(app.pid, app.savedPriority);
if (app.renderThreadTid != 0) {
Process.setThreadScheduler(app.renderThreadTid,
Process.SCHED_OTHER, 0);
Process.setThreadPriority(app.renderThreadTid, -4);
}
} else { // 恢复前台UI相关线程优先级为0
// Reset priority for top app UI and render threads
Process.setThreadPriority(app.pid, 0);
if (app.renderThreadTid != 0) {
Process.setThreadPriority(app.renderThreadTid, 0);
}
}
}
} catch (Exception e) {
Slog.w(TAG, "Failed setting process group of " + app.pid
+ " to " + app.curSchedGroup);
e.printStackTrace();
} finally {
Binder.restoreCallingIdentity(oldId);
}
}
}
总结下,主要做了两件事,参考代码里的中文注释:
1、调用Process.setProcessGroup(int pid, int group)设置进程调度策略,native层代码不再详细分析,原理是利用linux的cgroup机制,将进程根据状态放入预先设定的cgroup分组中,这些分组包含了对cpu使用率、cpuset、cpu调频等子资源的配置,已满足特定状态进程对系统资源的需求。
2、当schedGroup在ProcessList.SCHED_GROUP_TOP_APP跟非ProcessList.SCHED_GROUP_TOP_APP间切换时调用Process.setThreadPriority(int tid, int priority),即应用进入前台和退出前台时改变UI相关线程优先级,这里的UI相关线程包括主线程和RenderThread,以提升用户界面的响应速度。
综上所述,三个状态各司其职,从不同方面为保证应用的流畅运行保驾护航,缺一不可。