admin管理员组

文章数量:1558047

[ Android实战 ] 后台进程长时间占用CPU被杀?excessive cpu 495132 during 300001 dur=473068 limit=25

  • 背景
  • 日志分析
  • 源码分析
  • 为什么 CPU 占用超过 100%?
  • 总结

背景

客户反馈自己写的Launcher应用在后台运行几分钟就很容易被杀。
一开始没有提供日志,第一反应是lmk把应用杀掉了,因为我们的机器内存只有1G…
但是客户应用是Launcher应用,按理来说应该没那么容易被杀。还是得通过日志才能进一步分析

日志分析

客户提供完整日志后,通过应用的日志可以直接查到 pid,再过滤 pid,即可得到应用相关的所有日志,可以找到应用被杀的地方。

08-28 16:17:01.275   845   873 I am_kill : [0,19785,xxx.xxx.xxx,600,excessive cpu 495132 during 300001 dur=473068 limit=25]
08-28 16:17:01.494   845  1825 I am_proc_died: [0,19785,xxx.xxx.xxx,600,15]
08-28 16:17:01.525   845   876 I libprocessgroup: Successfully killed process cgroup uid 10233 pid 19785 in 247ms
08-28 16:17:01.532   367   367 I Zygote  : Process 19785 exited due to signal 9 (Killed)
08-28 16:17:01.626   845   867 W ActivityManager: setHasOverlayUi called on unknown pid: 19785

从字面上理解,就是应用长时间占用 CPU 导致应用被系统杀掉了。不过还是得研究下源码,确认下系统到底是如何查杀的。

源码分析

在 frameworks 下搜索 “excessive cpu” 关键字,即可在 ActivityManagerSerice.java 中看到相关逻辑。

应用查杀的逻辑很简单,就是遍历所有后台应用进程,如果在某个时间段内 CPU 使用时间超过阈值,则将该进程杀掉。

final void checkExcessivePowerUsageLocked() {
	......
	int i = mProcessList.mLruProcesses.size();
	while (i > 0) {
		i--;
		ProcessRecord app = mProcessList.mLruProcesses.get(i);
		if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
			......
			if (doCpuKills && uptimeSince > 0) {
				......
				if (((cputimeUsed*100)/uptimeSince) >= cpuLimit) {
					......
					app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
							+ " dur=" + checkDur + " limit=" + cpuLimit, true);
					......
				}
			}
			app.lastCpuTime = app.curCpuTime;
		}
	}
}

checkExcessivePowerUsageLocked 是通过 CHECK_EXCESSIVE_POWER_USE_MSG 的消息进行调用的,并且它是一个定时任务,时间间隔为 POWER_CHECK_INTERVAL

case CHECK_EXCESSIVE_POWER_USE_MSG: {
	synchronized (ActivityManagerService.this) {
		checkExcessivePowerUsageLocked();
		removeMessages(CHECK_EXCESSIVE_POWER_USE_MSG);
		Message nmsg = obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
		sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
	}
} break;

继续搜索,可以发现 CHECK_EXCESSIVE_POWER_USE_MSG 这个任务是在 AMS 的 finishBooting 阶段就开始的。

final void finishBooting() {
    ......
    // Start looking for apps that are abusing wake locks.
    Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
    mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
    // Tell anyone interested that we are done booting!
    SystemProperties.set("sys.boot_completed", "1");
	......
}

任务时间间隔和阈值在 ActivityManagerConstants.java 中定义:

private static final long DEFAULT_POWER_CHECK_INTERVAL = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000;  // 每5分钟检查一次
private static final int DEFAULT_POWER_CHECK_MAX_CPU_1 = 25;  // 5分钟内CPU使用时间超过25%则杀掉进程
private static final int DEFAULT_POWER_CHECK_MAX_CPU_2 = 25;  // 10分钟内CPU使用时间超过25%则杀掉进程
private static final int DEFAULT_POWER_CHECK_MAX_CPU_3 = 10;  // 15分钟内CPU使用时间超过10%则杀掉进程
private static final int DEFAULT_POWER_CHECK_MAX_CPU_4 = 2;  // 15分钟后CPU使用时间超过2%则杀掉进程

总结起来就是:系统启动后,会定时检测后台进程的 CPU 消耗情况,如果在某个时间段内超过阈值,则会将进程杀死。

为什么 CPU 占用超过 100%?

还有一个很奇怪的问题,既然是某个时间段内的 CPU 占用时间,为什么会出现 CPU 占用超过 100% 的情况?

08-28 16:17:01.275   845   873 I am_kill : [0,19785,xxx.xxx.xxx,600,excessive cpu 495132 during 300001 dur=473068 limit=25]

对应源码:cputimeUsed = 495132uptimeSince = 300001checkDur = 473068,为什么会出现 CPU 使用时间比检测时间间隔还长的情况,难道系统有 BUG?

转念一想,其实也不难理解,Android 设备的 CPU 都是多核的,当存在多线程执行任务时,CPU 的使用时间自然可能出现 double 甚至 triple 的情况

写一个简单的 demo,多线程执行最简单的 while 累加任务,把应用放到后台去跑,马上复现了跟客户类似的日志。

09-02 15:07:12.050   698   725 W ActivityManager: Killing 8728:com.example.test/u0a100 (adj 700): excessive cpu 650590 during 300035 dur=432717 limit=25
09-02 15:07:12.162   337   337 I Zygote  : Process 8728 exited due to signal 9 (Killed)
09-02 15:07:12.171   698   719 W ActivityManager: setHasOverlayUi called on unknown pid: 8728
09-02 15:07:12.173   698   728 I libprocessgroup: Successfully killed process cgroup uid 10100 pid 8728 in 121ms

总结

分析到现在已经基本结束了。总结一下,当日志中出现 excessive cpu 导致应用被杀时,可以针对不同情况进行处理:

1、首先,大概率是客户应用自身的逻辑问题,当应用进入后台时,显然不应该长时间占用 CPU。可以关注进入后台后,应用是否在执行某些耗时任务,是否反复执行,这块逻辑能否优化?

2、如果客户应用本身无法修改,可以根据实际使用场景,评估是否应用将客户应用加到白名单中,修改也很简单,在 checkExcessivePowerUsageLocked 针对应用包名跳过检查即可。

本文标签: 长时间被杀实战后台进程