转载

Android Anatomy:Android系统中的进程管理:内存的回收

本文是Android系统进程管理的第三篇文章,也是最后一篇。

进程管理的前面两篇文章,请参见这里:

  • Android系统中的进程管理:进程的创建
  • Android系统中的进程管理:进程的优先级

本文适合Android平台的应用程序开发者,也适合对于Android系统内部实现感兴趣的读者。

前言

内存是系统非常宝贵的资源,即便如今的移动设备上,内存已经达到4G甚至6G的级别,但对于内存的回收也依然重要,因为在Android系统上,同时运行的进程有可能会有几十甚至上百个之多。

如何将系统内存合理的分配给每个进程,以及如何进程进行内存回收,便是操作系统需要处理的问题之一。

本文会讲解Android系统中内存回收相关的知识。对于内存回收,主要可以分为两个级别:

  • 进程内的内存回收:针对运行中的每个进程进行内存回收
  • 进程级的内存回收:通过杀死进程来进行内存回收

这其中,进程内的内存回收主要分为两个方面:

  • 通过虚拟机自身的垃圾回收机制来回收内存
  • 在系统内存状态发生变化时,通知应用程序,让开发者配合进行内存回收

而进程级的内存回收主要是依靠系统中的两个模块来进行回收,它们分别是:

  • Linux OOM Killer
  • LowMemoryKiller

Android系统的内存管理简介

在Android系统中,进程可以大致分为 系统进程应用进程 两大类。

系统进程是系统内置的(例如: initzygotesystem_server 进程),属于操作系统必不可少的一部分。系统进程的作用在于:

  • 管理硬件设备
  • 提供访问设备的基本能力
  • 管理应用进程

应用进程是指应用程序运行的进程。这些应用程序可能是系统出厂自带的(例如Launcher,电话,短信等应用),也可能是用户自己安装的(例如:微信,支付宝等)。

Android中应用进程通常都运行在Java虚拟机中。在Android 5.0之前的版本,这个虚拟机是Dalvik,5.0及之后版本,虚拟机称作Android Runtime,简称“ART”。

关于ART和Dalvik可以参见这里: ART and Dalvik

无论是Dalvik还是ART,本身都具有垃圾回收的能力,垃圾回收是指:虚拟机本身会监测应用程序的对象创建和使用。在某个对象不再被使用时,虚拟机可以将其销毁以回收内存。

垃圾回收的基本思想是:通过对象间的引用关系,通过遍历的方式计算出每个对象被引用的次数。而那些没有被任何其他对象引用的对象,便可以认为是不会再被使用到的,便可以将其释放掉。关于垃圾回收的更多信息,可以查阅维基百科: Garbage collection

Android的应用程序都会依赖一些公共的数据,例如:Android SDK提供的类和接口,以及Framework公开的资源文件等。为了达到节省内存的目前,这些数据在加载到内存中之后并不是每个应用进程单独一份拷贝。而是会在所有应用之间进行共享。关于这部分内容,我们已经在 Android系统中的进程管理:进程的创建 一文中讲解过。

在Java语言中,通过new创建的对象都会在Heap中分配内存。应用程序堆所允许的最大空间并不是整个物理内存的大小。系统会根据设备的物理内存来确定每个应用程序所允许使用的内存大小,一旦应用程序使用的内存超过这个大小,便会发生 OutOfMemoryError 。

关于如何监测应用程序的内存使用,可以参见这里: Investigating Your RAM Usage 。

开发者相关的API

虚拟机的垃圾回收

开发者的内存回收

Linux OOM Killer

Linux OOM Killer的源码在Linux内核中,路径是: mm/oom_kill.c

可以在这里直接查看: Linux/mm/oom_kill.c 。

Linux OOM Killer的基本想法是: 当系统已经没法再分配内存的时候,内核会遍历所有的进程,对每个进程计算badness值,得分最高(badness)的进程将被杀死

调用流程如下:

_alloc_pages -> out_of_memory() -> select_bad_process() -> oom_badness()

这其中, _alloc_pages 是内核在分配内存时,会调用的函数。那么,内核是如何计算进程的badness值的呢?请看下面的代码:

unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
			  const nodemask_t *nodemask, unsigned long totalpages)
{
	long points;
	long adj;

	...

	points = get_mm_rss(p->mm) + p->mm->nr_ptes +
		 get_mm_counter(p->mm, MM_SWAPENTS);
	task_unlock(p);

	if (has_capability_noaudit(p, CAP_SYS_ADMIN))
		points -= (points * 3) / 100;

	adj *= totalpages / 1000;
	points += adj;

	return points > 0 ? points : 1;
}

从这段代码中,我们可以看到,影响进程badness值的因素主要有三个:

  • 进程的oom_score_adj值
  • 进程的内存占用大小
  • 进程是否是root用户的进程

这其中,oom_score_adj值越小,进程占用的内存越小,并且如果是root用户的进程,就越是不容易被选择中。反之则反。(关于oom_score_adj在前面的内容中,我们已经介绍过了。)

LowMemoryKiller

Linux OOM Killer是在系统内存使用情况非常严峻的时候才会起作用。但直到这个时候才开始杀死进程来回收内存是有点晚的。因为在进程被杀死之前,其他进程都无法再申请内存了。

因此,Google在Android上新增了一个LowMemoryKiller模块。LowMemoryKiller通常会在Linux OOM Killer起作用之前,就开始杀死进程。

LowMemoryKiller的做法是:

提供6个可以设置的内存级别,当系统内存每低于一个级别时,将oom_score_adj大于某个指定值的进程全部杀死。

这么说会有些抽象,但具体看一下LowMemoryKiller的配置文件我们就好理解了。LowMemoryKiller在sysfs上暴露了两个文件来供系统调整参数,这两个文件的路径是:

  • /sys/module/lowmemorykiller/parameters/minfree
  • /sys/module/lowmemorykiller/parameters/adj

这两个文件是配对使用的,每个文件中都是有逗号分隔的6个整数值,在某个设备上,这两个文件的值可能分别是下面这样:

  • 18432,23040,27648,32256,55296,80640
  • 0,100,200,300,900,906

这组配置的含义是;当系统内存低于80640时,将oom_score_adj值大于906的进程全部杀死;当系统内存低于55296时,将oom_score_adj值大于900的进程全部杀死,其他类推。

LowMemoryKiller杀死进程的时候会在内核留下日志,你可以通过 dmesg 命令中看到。这个日志可能是这样的:

lowmemorykiller: Killing 'gnunet-service-' (service adj 0,
to free 327224kB on behalf of 'kswapd0' (21) because
cache 6064kB is below limit 6144kB for oom_score_adj 0

从这个日志中,我们可以看到被杀死进程的名称,进程pid和oom_score_adj值。另外还有,系统在杀死这个进程之前系统内存还剩多少,以及杀死这个进程释放了多少。

LowMemoryKiller的源码也在内核中,路径是: kernel/drivers/staging/android/lowmemorykiller.c

lowmemorykiller.c中定义了如下几个函数:

  • lowmem_shrink
  • lowmem_init
  • lowmem_exit
  • lowmem_oom_adj_to_oom_score_adj
  • lowmem_autodetect_oom_adj_values
  • lowmem_adj_array_set
  • lowmem_adj_array_get
  • lowmem_adj_array_free

LowMemoryKiller本身是一个内核驱动程序的形式存在, lowmem_initlowmem_exit 分别负责模块的初始化和退出清理工作。

lowmem_init 函数中,就是通过 register_shrinker 向内核中注册了 register_shrinker 函数:

static int __init lowmem_init(void)
{
	register_shrinker(&lowmem_shrinker);
	return 0;
}

register_shrinker 函数就是LowMemoryKiller的算法核心,这个函数的代码和说明如下:

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) -
						total_swapcache_pages();
   
	if (lowmem_adj_size < array_size)
		array_size = lowmem_adj_size;
	if (lowmem_minfree_size < array_size)
		array_size = lowmem_minfree_size;
	// lowmem_minfree 和lowmem_adj 记录了两个配置文件中配置的数据
	for (i = 0; i < array_size; i++) {
		minfree = lowmem_minfree[i];
		// 确定当前系统处于低内存的第几档
		if (other_free < minfree && other_file < minfree) {
		   // 确定需要杀死的进程的oom_score_adj的上限
			min_score_adj = lowmem_adj[i];
			break;
		}
	}
	if (sc->nr_to_scan > 0)
		lowmem_print(3, "lowmem_shrink %lu, %x, ofree %d %d, ma %hd/n",
				sc->nr_to_scan, sc->gfp_mask, other_free,
				other_file, min_score_adj);
	rem = global_page_state(NR_ACTIVE_ANON) +
		global_page_state(NR_ACTIVE_FILE) +
		global_page_state(NR_INACTIVE_ANON) +
		global_page_state(NR_INACTIVE_FILE);
	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);
		return rem;
	}
	selected_oom_score_adj = min_score_adj;

	rcu_read_lock();
	// 遍历所有进程
	for_each_process(tsk) {
		struct task_struct *p;
		short oom_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();
			return 0;
		}
		oom_score_adj = p->signal->oom_score_adj;
		// 跳过那些oom_score_adj值比目标值小的
		if (oom_score_adj < min_score_adj) {
			task_unlock(p);
			continue;
		}
		tasksize = get_mm_rss(p->mm);
		task_unlock(p);
		if (tasksize <= 0)
			continue;
		// selected 是将要杀死的备选进程
		if (selected) {
		   // 跳过那些oom_score_adj比备选的小的
			if (oom_score_adj < selected_oom_score_adj)
				continue;
		   // 如果oom_score_adj一样,跳过那些内存消耗更小的
			if (oom_score_adj == selected_oom_score_adj &&
			    tasksize <= selected_tasksize)
				continue;
		}
		// 可能要更换备选的目标,应该又发现一个oom_score_adj更大,
		// 或者内存消耗更大的
		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) {
		long cache_size = other_file * (long)(PAGE_SIZE / 1024);
		long cache_limit = minfree * (long)(PAGE_SIZE / 1024);
		long free = other_free * (long)(PAGE_SIZE / 1024);
		trace_lowmemory_kill(selected, cache_size, cache_limit, free);
		lowmem_print(1, "Killing '%s' (%d), adj %hd,/n" /
				"   to free %ldkB on behalf of '%s' (%d) because/n" /
				"   cache %ldkB is below limit %ldkB for oom_score_adj %hd/n" /
				"   Free memory is %ldkB above reserved/n",
			     selected->comm, selected->pid,
			     selected_oom_score_adj,
			     selected_tasksize * (long)(PAGE_SIZE / 1024),
			     current->comm, current->pid,
			     cache_size, cache_limit,
			     min_score_adj,
			     free);

		lowmem_deathpending_timeout = jiffies + HZ;
		set_tsk_thread_flag(selected, TIF_MEMDIE);
		send_sig(SIGKILL, selected, 0);
		rem -= selected_tasksize;
	}
	lowmem_print(4, "lowmem_shrink %lu, %x, return %d/n",
		     sc->nr_to_scan, sc->gfp_mask, rem);
	rcu_read_unlock();
	return rem;
}

进程的死亡处理

在任何时候,应用进程都可能死亡,例如被OOM Killer或者LowMemoryKiller杀死,又或者被用户手动杀死。无论哪种情况,作为应用进程的管理者ActivityManagerService都需要知道。

在应用进程死亡之后,ActivityManagerService需要执行如下工作:

  • 执行清理工作 ActivityManagerService内部的ProcessRecord以及可能存在的四大组件的相关结构需要全部清理干净
  • 重新计算进程的优先级 上文已经提到过,进程的优先级是有关联性的,有其中一个进程死亡了,可能会连到影响到其他进程的优先级需要调整。

ActivityManagerService是利用Binder提供的死亡通知机制来进行进程的死亡处理的。关于Binder请参阅其他资料,限于篇幅关系,这里不再展开讲解。

简单来说,死亡通知机制就提供了进程间的一种死亡监听的能力:当目标进程死亡的时候,监听回调会执行。

ActivityManagerService中的AppDeathRecipient监听了应用进程的死亡消息,该类代码如下:

private final class AppDeathRecipient implements IBinder.DeathRecipient {
   final ProcessRecord mApp;
   final int mPid;
   final IApplicationThread mAppThread;

   AppDeathRecipient(ProcessRecord app, int pid,
           IApplicationThread thread) {
       if (DEBUG_ALL) Slog.v(
           TAG, "New death recipient " + this
           + " for thread " + thread.asBinder());
       mApp = app;
       mPid = pid;
       mAppThread = thread;
   }

   @Override
   public void binderDied() {
       if (DEBUG_ALL) Slog.v(
           TAG, "Death received in " + this
           + " for thread " + mAppThread.asBinder());
       synchronized(ActivityManagerService.this) {
           appDiedLocked(mApp, mPid, mAppThread, true);
       }
   }
}

每一个应用进程在启动之后,都会attach到ActivityManagerService上通知其自身进程启动完成了。这时ActivityManagerService便会为其创建一个死亡通知的监听器。在这之后进程死亡了,ActivityManagerService便会收到通知。

private final boolean attachApplicationLocked(IApplicationThread thread,
       int pid) {
    ...
        try {
            AppDeathRecipient adr = new AppDeathRecipient(
                    app, pid, thread);
            thread.asBinder().linkToDeath(adr, 0);
            app.deathRecipient = adr;
        } catch (RemoteException e) {
            app.resetPackageList(mProcessStats);
            startProcessLocked(app, "link fail", processName);
            return false;
        }
    ...
}

进程死亡之后的处理工作是 appDiedLocked 这个方法中处理的,这部分还是比较容易理解的,这里就不过多讲解了。

结束语

进程的管理本身是一个非常大的话题,本文尽可能的提及了Android系统中对于进程管理的主要内容。 但由于篇幅关系,有些内容可能还不够深入,这就需要读者自行去Read The Fxxking Source Code了。 又由于笔者本身水平有限,错谬之处难免,也欢迎大家留言指出和讨论。

参考资料与推荐读物

Overview of Android Memory Management

Manage Your App’s Memory

Processes and Threads

Investigating Your RAM Usage

Debugging ART Garbage Collection

Android Runtime

Taming the OOM killer

OOM Killer

Out Of Memory Management

原文  http://qiangbo.space/2016-12-08/AndroidAnatomy_Process_Recycle/
正文到此结束
Loading...