Android内存管理机制详解

2021年9月17日 12点热度 0条评论 来源: 柴华松

本文主要介绍Android内存相关,最新已整理的Google官方文档《Android内存管理机制官方详解文档》,请各位参阅。

与windows内存区别

        在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然。这是Linux内存管理的一个优秀特性,在这方面,区别于 Windows的内存管理。主要特点是,无论物理内存有多大,Linux都将其充份利用,将一些程序调用过的硬盘数据读入内存,利用内存读写的高速特性来提高Linux系统的数据访问性能。而Windows是只在需要内存时,才为应用程序分配内存,并不能充分利用大容量的内存空间。换句话说,每增加一些物理内存,Linux都将能充分利用起来,发挥了硬件投资带来的好处,而Windows只将其做为摆设,即使增加8GB甚至更大。

android内存的意义

        其实我们在用安卓手机的时候不用太在意剩余内存,Android上的应用是java,当然需要虚拟机,而android上的应用是带有独立虚拟机的,也就是每开一个应用就会打开一个独立的虚拟机。其实和java的垃圾回收机制类似,系统有一个规则来回收内存。进行内存调度有个阀值,只有低于这个值系统才会按一个列表来关闭用户不需要的东西。当然这个值默认设置得很小,所以你会看到内存老在很少的数值徘徊。但事实上他并不影响速度。相反加快了下次启动应用的速度。这本来就是 android标榜的优势之一,如果人为去关闭进程,没有太大必要。特别是使用自动关进程的软件。为什么内存少的时候运行大型程序会慢呢,原因是:在内存剩余不多时打开大型程序时会触发系统自身的调进程调度策略,这是十分消耗系统资源的操作,特别是在一个程序频繁向系统申请内存的时候。这种情况下系统并不会关闭所有打开的进程,而是选择性关闭,频繁的调度自然会拖慢系统。

 

进程管理软件

        进程管理软件有无必要呢?有的。就是在运行大型程序之前,你可以手动关闭一些进程释放内存,可以显著的提高运行速度。但一些小程序完全可交由系统自己管理。那么如果不关程序是不是会更耗电。android的应用在被切换到后台时,它其实已经被暂停了,并不会消耗cpu资源只保留了运行状态。所以为什么有的程序切出去重进会到主界面。但是一个程序如果想要在后台处理些东西,如音乐播放,它就会开启一个服务。服务可在后台持续运行,所以在后台耗电的也只有带服务的应用了。我们可以把带服务的进程用进程管理软件关闭就可以了。没有带服务的应用在后台是完全不耗电的没有必要关闭。这种设计本来就是一个非常好的设计,下次启动程序时会更快,因为不需要读取界面资源,何必要关掉他们抹杀这个android的优点呢。

Android进程种类

1.       前台进程(foreground)

        目前正在屏幕上显示的进程和一些系统进程。举例来说,Dialer,Storage,Google Search等系统进程就是前台进程;再举例来说,当你运行一个程序,如浏览器,当浏览器界面在前台显示时,浏览器属于前台进程(foreground),但一旦你按home回到主界面,浏览器就变成了后台程序(background)。我们最不希望终止的进程就是前台进程。

2.       可见进程(visible)

        可见进程是一些不再前台,但用户依然可见的进程,举个例来说:widget、输入法等,都属于visible。这部分进程虽然不在前台,但与我们的使用也密切相关,我们也不希望它们被终止(你肯定不希望时钟、天气,新闻等widget被终止,那它们将无法同步,你也不希望输入法被终止,否则你每次输入时都需要重新启动输入法)

3.       桌面进程(home app)

        即launcher,保证在多任务切换之后,可以快速返回到home界面而不需重新加载launcher

4.       次要服务(secondary server)

        目前正在运行的一些服务(主要服务,如拨号等,是不可能被进程管理终止的,故这里只谈次要服务),举例来说:谷歌企业套件,Gmail内部存储,联系人内部存储等。这部分服务虽然属于次要服务,但很一些系统功能依然息息相关,我们时常需要用到它们,所以也太希望他们被终止

5.       后台进程(hidden)

        即是后台进程(background),就是我们通常意义上理解的启动后被切换到后台的进程,如浏览器,阅读器等。当程序显示在屏幕上时,他所运行的进程即为前台进程(foreground),一旦我们按home返回主界面(注意是按home,不是按back),程序就驻留在后台,成为后台进程(background)。后台进程的管理策略有多种:有较为积极的方式,一旦程序到达后台立即终止,这种方式会提高程序的运行速度,但无法加速程序的再次启动;也有较消极的方式,尽可能多的保留后台程序,虽然可能会影响到单个程序的运行速度,但在再次启动已启动的程序时,速度会有所提升。这里就需要用户根据自己的使用习惯找到一个平衡点

6.       内容供应节点(content provider)

        没有程序实体,进提供内容供别的程序去用的,比如日历供应节点,邮件供应节点等。在终止进程时,这类程序应该有较高的优先权

7.       空进程(empty)

        没有任何东西在内运行的进程,有些程序,比如BTE,在程序退出后,依然会在进程中驻留一个空进程,这个进程里没有任何数据在运行,作用往往是提高该程序下次的启动速度或者记录程序的一些历史信息。这部分进程无疑是应该最先终止的。

幽灵刽子手LMK (Low Memory Killer)

 

执行条件

        剩余内存小于应用定义的APP_MEM值,开始查看adj值列表,kill相应程序。

实现机制

Low Memory Killer的源代码在kernel/drivers/staging/android/lowmemorykiller.c中

module_init(lowmem_init);

module_exit(lowmem_exit);

    模块加载和退出的函数,主要的功能就是register_shrinker和unregister_shrinker结构体lowmem_shrinker。主要是将函数lowmem_shrink注册到shrinker链表里,在mm_scan调用。

下面详细的介绍这个函数:

for (i = 0; i < array_size; i++) {
        if (other_file < lowmem_minfree[i]) {
            min_adj = lowmem_adj[i];
            break;
        }
    }

other_file, 系统的空闲内存数,根据上面的逻辑判断出,low memory killer需要对adj高于多少(min_adj)的进程进行分析是否释放。

 if (nr_to_scan <= 0 || min_adj == OOM_ADJUST_MAX + 1) {
        lowmem_print(5, "lowmem_shrink %d, %x, return %d\n",
                 nr_to_scan, gfp_mask, rem);
        return rem;
    }

  判断,系统当前的状态是否需要进行low memory killer。

for_each_process(p) {
        struct mm_struct *mm;
        struct signal_struct *sig;
        int oom_adj;
        task_lock(p);
        mm = p->mm;
        sig = p->signal;
        if (!mm || !sig) {
            task_unlock(p);
            continue;
        }
        oom_adj = sig->oom_adj;
        if (oom_adj < min_adj) {
            task_unlock(p);
            continue;
        }
        tasksize = get_mm_rss(mm);
        task_unlock(p);
        if (tasksize <= 0)
            continue;
        if (selected) {
            if (oom_adj < selected_oom_adj)
                continue;
            if (oom_adj == selected_oom_adj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;
        selected_oom_adj = oom_adj;
        lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
                 p->pid, p->comm, oom_adj, tasksize);
    }

对每个sig->oom_adj大于min_adj的进程,找到占用内存最大的进程存放在selected中。

if (selected) {
        if (fatal_signal_pending(selected)) {
            pr_warning("process %d is suffering a slow death\n",
                   selected->pid);
            read_unlock(&tasklist_lock);
            return rem;
        }
        lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",
                 selected->pid, selected->comm,
                 selected_oom_adj, selected_tasksize);
        force_sig(SIGKILL, selected);
        rem -= selected_tasksize;
    }

发送SIGKILL信息,杀掉该进程。

        在了解了其机制和原理之后,我们发现它的实现非常简单,与标准的Linux OOM机制类似,只是实现方式稍有不同。标准Linux的OOM Killer机制在mm/oom_kill.c中实现,且会被__alloc_pages_may_oom调用(在分配内存时,即mm/page_alloc.c中)。oom_kill.c最主要的一个函数是out_of_memory,它选择一个bad进程Kill,Kill的方法同样是通过发送SIGKILL信号。在out_of_memory中通过调用select_bad_process来选择一个进程Kill,选择的依据在badness函数中实现,基于多个标准来给每个进程评分,评分最高的被选中并Kill。一般而言,占用内存越多,oom_adj就越大,也就越有可能被选中。

资源配置

阈值表可以通过/sys/module/lowmemorykiller/parameters/adj和/sys/module/lowmemorykiller/parameters/minfree进行配置,例如在init.rc中:

# Write value must be consistent with the above properties.

   write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15
   write /proc/sys/vm/overcommit_memory 1
   write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144

   class_start default

进程oom_adj同样可以进行设置,通过write /proc/<PID>/oom_adj ,在init.rc中,init进程的pid为1,omm_adj被配置为-16,永远不会被杀死。

 # Set init its forked children's oom_adj.

   write /proc/1/oom_adj -16

     Low memory killer的基本原理我们应该弄清了,正如我前面所说的,进程omm_adj的大小跟进程的类型以及进程被调度的次序有关。进程的类型,可以在ActivityManagerService中清楚的看到:

    static final int EMPTY_APP_ADJ;
    static final int HIDDEN_APP_MAX_ADJ;
    static final int HIDDEN_APP_MIN_ADJ;
    static final int HOME_APP_ADJ;
    static final int BACKUP_APP_ADJ;
    static final int SECONDARY_SERVER_ADJ;
    static final int HEAVY_WEIGHT_APP_ADJ;
    static final int PERCEPTIBLE_APP_ADJ;
    static final int VISIBLE_APP_ADJ;
    static final int FOREGROUND_APP_ADJ;
    static final int CORE_SERVER_ADJ = -12;
    static final int SYSTEM_ADJ = -16;

  ActivityManagerService定义各种进程的oom_adj,CORE_SERVER_ADJ代表一些核心的服务的omm_adj,数值为-12,由前面的分析可知道,这类进程永远也不会被杀死。

在init.rc中: 

# Define the oom_adj values for the classes of processes that can be
# killed by the kernel.  These are used in ActivityManagerService.
    setprop ro.FOREGROUND_APP_ADJ 0
    setprop ro.VISIBLE_APP_ADJ 1
    setprop ro.HOME_APP_ADJ 1
    setprop ro.PERCEPTIBLE_APP_ADJ 2
    setprop ro.HEAVY_WEIGHT_APP_ADJ 3
    setprop ro.SECONDARY_SERVER_ADJ 4
    setprop ro.BACKUP_APP_ADJ 5
    setprop ro.HIDDEN_APP_MIN_ADJ 7
    setprop ro.EMPTY_APP_ADJ 15

# Define the memory thresholds at which the above process classes will
# be killed.  These numbers are in pages (4k).
    setprop ro.FOREGROUND_APP_MEM 2048
    setprop ro.VISIBLE_APP_MEM 3072
    setprop ro.HOME_APP_MEM 3072
    setprop ro.PERCEPTIBLE_APP_MEM 4096
    setprop ro.HEAVY_WEIGHT_APP_MEM 4096
    setprop ro.SECONDARY_SERVER_MEM 10240
    setprop ro.BACKUP_APP_MEM 10240
    setprop ro.HIDDEN_APP_MEM 10240
    setprop ro.EMPTY_APP_MEM 14336

# Write value must be consistent with the above properties.
# Note that the driver only supports 6 slots, so we have combined some of
# the classes into the same memory level; the associated processes of higher
# classes will still be killed first.
    write /sys/module/lowmemorykiller/parameters/adj 0,1,2,4,7,15

    write /proc/sys/vm/overcommit_memory 1
    write /proc/sys/vm/min_free_order_shift 4
  write /sys/module/lowmemorykiller/parameters/minfree 2048,3072,4096,10240,10240,14336

    # Set init its forked children's oom_adj.
    write /proc/1/oom_adj -16

ActivityManagerService.java

打开程序或者有程序进入后台时都会执行updateOomAdjLocked()函数:

  

    以上就是android内存管理机制的内容了,在一些设备内存比较低的情况下,我们可以对其内存进行优化,从而让我们的设备运行的更加流畅。

    原文作者:柴华松
    原文地址: https://blog.csdn.net/chaihuasong/article/details/8289367
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。