Android OOM、OOMKillery以及LMK相关概念

OOM

oom(out of memory)
Android系统对 dalvik 的 vm heapsize 作了硬性限制,当 java 进程申请的 java 空间超过阈值时,就会抛出OOM异常。可以通过adb shell getprop 或者 getprop dalvik.vm.heapgrowthlimit 查看此阈值。
Android OOM、OOMKillery以及LMK相关概念

getprop dalvik.vm.heapgrowthlimit                                  
256m

我们用下面的代码获取heapsize:

ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int heapSize = manager.getMemoryClass();
int maxHeapSize = manager.getLargeMemoryClass();  // manafest.xml   android:largeHeap="true"

程序发生 OMM 并不表示 RAM 不足,而是因为程序申请的 java heap 对象超过了 dalvik vm heapgrowthlimit。也就是说,在 RAM 充足的情况下,也可能发生 OOM(这句话比较关键)

这样设计的目的是为了让 Android 系统能同时让比较多的进程常驻内存,这样程序启动时就不用每次都重新加载到内存,能够给用户更快的响应。迫使每个应用程序使用较小的内存,移动设备非常有限的RAM就能使比较多的app常驻其中。

进程的内存空间只是虚拟内存(或者叫作逻辑内存),而程序的运行需要的是实实在在的内存,即物理内存(RAM)。在必要时,操作系统会将程序运行中申请的内存(虚拟内存)映射到RAM,让进程能够使用物理内存。

以下两个概率区分Dalvik Heap 和Native Heap

native进程:采用C/C++实现,不包含dalvik实例的linux进程,/system/bin/目录下面的程序文件运行后都是以native进程形式存在的

java进程:实例化了 dalvik 虚拟机实例的 linux 进程,进程的入口 main 函数为 java 函数

130|console:/ # dumpsys meminfo com.funshion.ottedu                            
Applications Memory Usage (in Kilobytes):
Uptime: 9506057 Realtime: 9506057

** MEMINFO in pid 1187 [com.funshion.ottedu] **
                   Pss  Private  Private     Swap      Rss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty    Total     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------   ------
  Native Heap    33913    33848        0        0    35692    48244    34778     2811
  Dalvik Heap     4135     3960        0        0     7220    12945     6473     6472
 Dalvik Other     2468     1940        0        0     3288                           
        Stack     1012     1012        0        0     1016                           
       Ashmem        2        0        0        0        8                           
    Other dev      104        0      104        0      324                           
     .so mmap     5411      204      300        0    41620                           
    .jar mmap     2859        0       40        0    28928                           
    .apk mmap     6124      180     2520        0    11280                           
    .ttf mmap       92        0        0        0      312                           
    .dex mmap    14715       20    14608        0    14960                           
    .oat mmap       75        0        0        0     2148                           
    .art mmap     6362     6052        0        0    14632                           
   Other mmap     2098       40      924        0     5184                           
    GL mtrack    37872    37872        0        0    37872                           
      Unknown     3686     3684        0        0     3920                           
        TOTAL   120928    88812    18496        0   120928    61189    41251     9283

C/C++ 申请的内存空间在 native heap 中,而 java 申请的内存空间则在 dalvik heap中

Java 程序发生 OMM 并不是表示 RAM 不足。
如果 RAM 真的不足,会发生什么呢?这时 Android 的 memory killer 会起作用(OOMKiller),当 RAM 所剩不多时,memory killer 会杀死一些优先级比较低的进程来释放物理内存,让高优先级程序得到更多的内存。

参考链接:Android 中的 Dalvik Heap 和 Native Heap


OOMKiller

OOMKiller 是Linux Kernel 的内存监控机制。会在内存紧张的时候,会依次kill内存占用较高的进程,并在/var/log/message中进行记录。里面会记录一些如pid,process name,cpu mask,trace等信息,通过监控可以发现类似问题。

Mar 10 23:40:13 ...... kernel: xxx invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
Mar 10 23:40:13 ...... kernel: [<ffffffff811171b1>] oom_kill_process+0x241/0x390
Mar 10 23:40:13 ...... kernel: [<ffffffff81116c4d>] ? oom_unkillable_task+0xcd/0x120
Mar 10 23:40:13 ...... kernel: [ pid ]   uid  tgid total_vm rss nr_ptes swapents oom_score_adj 

在我们底层通过Malloc 申请内存,根据Linux有一种内存机制,会返回一个非空的内存,但是Linux并不保证这些内存马上可用(虚拟内存->物理内存),当你需要使用的物理内存不足时,也会出发OOM Killer.
内核代码为:common/mm/oom_kill.c.
其调用栈为:

malloc()
	 _alloc_pages()
	 	 out_of_memory() 
	 	 	select_bad_process()
	 		oom_kill_process()
	 			
common/mm/oom_kill.c
int sysctl_panic_on_oom;//内存不够时内核是否直接panic
int sysctl_oom_kill_allocating_task;//是否选择当前正在申请内存的进程进行kill
bool out_of_memory(struct oom_control *oc)
{
	...
	constraint = constrained_alloc(oc);
	if (constraint != CONSTRAINT_MEMORY_POLICY)
		oc->nodemask = NULL;
	check_panic_on_oom(oc, constraint);

	if (!is_memcg_oom(oc) && sysctl_oom_kill_allocating_task &&
	    current->mm && !oom_unkillable_task(current, NULL, oc->nodemask) &&
	    current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {
		get_task_struct(current);
		oc->chosen = current;
		oom_kill_process(oc, "Out of memory (oom_kill_allocating_task)");
		return true;
	}

	select_bad_process(oc);
	if (oc->chosen && oc->chosen != (void *)-1UL) {
		oom_kill_process(oc, !is_memcg_oom(oc) ? "Out of memory" :
				 "Memory cgroup out of memory");
		schedule_timeout_killable(1);
	}
	...
}

Kill 策略:

1)计算该进程以及其子进程所占用的内存;
2)计算CPU时间和存活时间
3)计算进程优先级等/proc/pids/oom_score,/proc/pids/oom_adj;
占用内存越高,得分越高,cpu时间和存活时间越高,得分越低;进程优先级越高,得分越低
综合上述因素后,会得到一个point,得分最高的会被选中,然后被kill掉

LMK

基于 Linux Kernel 的 OOMKiller 思想,Android 系统拓展出了自己的内存监控体系 : Low Memory Killer.相比 Linux 达到临界值才触发,Android 实现了不同梯级的 Killer。

横向比较oomkiller Vs lmk:
OOMkiller(Out Of Memory Killer)是在Linux系统无法分配新内存的时候,选择性杀掉进程,到oom的时候,系统可能已经不太稳定,而LowMemoryKiller是一种根据内存阈值级别触发的内存回收的机制,在系统可用内存较低时,就会选择性杀死进程的策略,相对OOMKiller,更加灵活。

先来看看一下两个文件节点:

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

我们可以修改以上两个文件节点的值:

write /sys/module/lowmemorykiller/parameters/adj        0, 8
write /sys/module/lowmemorykiller/parameters/minfree 1024, 4096

取值说明:当系统可用内存低于4096个pages时,则会杀掉oom_score_adj>=8的进程

ActivityManagerService 在运行时会根据当前的系统配置自动调整 adj 和 minfree.
AMS.updateOomLevels 方法也是通过修改上面两个文件来实现的。

ADJ优先级 优先级 优先级
UNKNOWN_ADJ 16 一般指将要会缓存进程,无法获取确定值
CACHED_APP_MAX_ADJ 15 不可见进程的adj最大值(不可见进程可能在任何时候被杀死)
CACHED_APP_MIN_ADJ 9 不可见进程的adj最小值(不可见进程可能在任何时候被杀死)
SERVICE_B_AD 8 B List中的Service(较老的、使用可能性更小)
PREVIOUS_APP_ADJ 7 上一个App的进程(比如APP_A跳转APP_B,APP_A不可见的时候,A就是属于PREVIOUS_APP_ADJ)
HOME_APP_ADJ 6 Home进程
SERVICE_ADJ 5 服务进程(Service process)
HEAVY_WEIGHT_APP_ADJ 4 后台的重量级进程,system/rootdir/init.rc文件中设置
BACKUP_APP_ADJ 3 备份进程(这个不太了解)
PERCEPTIBLE_APP_ADJ 2 可感知进程,比如后台音乐播放
VISIBLE_APP_ADJ 1 可见进程(可见,但是没能获取焦点,比如新进程仅有一个悬浮Activity,Visible process)
FOREGROUND_APP_ADJ 0 前台进程(正在展示是APP,存在交互界面,Foreground process)
PERSISTENT_SERVICE_ADJ -11 关联着系统或persistent进程
PERSISTENT_PROC_ADJ -12 系统persistent进程,比如telephony
SYSTEM_ADJ -16 系统进程
NATIVE_ADJ -17 native进程(不被系统管理)

从Android 7.0开始,ADJ采用100、200、300;在这之前的版本ADJ采用数字1、2、3,这样的调整可以更进一步地细化进程的优先级,比如在VISIBLE_APP_ADJ(100)与PERCEPTIBLE_APP_ADJ(200)之间,可以有ADJ=101、102级别的进程。

Android应用的优先级是如何更新的
在Android家族,5.0之前的系统进程优先级是AMS进程直接修改的(通过了Linux中的一个proc文件体统),
5.0之后,是修改优先级的操作被封装成了一个独立的服务-lmkd,lmkd服务位于用户空间,其作用层次同AMS、WMS类似,就是一个普通的系统服务(其实就是AMS通过socket向lmkd服务发送请求,让lmkd去更新进程的优先级)

AMS 更新进程优先级:
1)AMS.applyOomAdjLocked(),会设置某个进程的adj;
2)AMS.updateConfiguration(),会更新整个各个级别的oom_adj信息.
3)AMS.cleanUpApplicationRecordLocked()或者handleAppDiedLocked(),会将某个进程从lmkd策略中移除.

lmkd:

system/core/rootdir/init.rc
 start lmkd
system/memory/lmkd/lmkd.rc
system/memory/lmkd/lmkd.c
system/memory/lmkd/lmkd.c
//更新优先级
static void cmd_procprio(LMKD_CTRL_PACKET packet) {
    struct proc *procp;
    char path[80];
    char val[20];
    int soft_limit_mult;
    struct lmk_procprio params;

    lmkd_pack_get_procprio(packet, &params);

    if (params.oomadj < OOM_SCORE_ADJ_MIN ||
        params.oomadj > OOM_SCORE_ADJ_MAX) {
        ALOGE("Invalid PROCPRIO oomadj argument %d", params.oomadj);
        return;
    }

    snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);
    snprintf(val, sizeof(val), "%d", params.oomadj);
    writefilestring(path, val);

    if (use_inkernel_interface)
        return;

    ...
}
static int kill_one_process(struct proc* procp, int min_score_adj,
                            enum vmpressure_level level) {
    ...
    r = kill(pid, SIGKILL);
    ALOGI(
        "Killing '%s' (%d), uid %d, adj %d\n"
        "   to free %ldkB because system is under %s memory pressure oom_adj %d\n",
        taskname, pid, uid, procp->oomadj, tasksize * page_k,
        level_name[level], min_score_adj);
    pid_remove(pid);
	...
}

LomemoryKiller 杀死进程(内核)
shrinker
LMK驱动通过注册shrinker来实现的,shrinker是linux kernel标准的回收内存page的机制,由内核线程kswapd负责监控。当内存不足时kswapd线程会遍历一张shrinker链表,并回调已注册的shrinker函数来回收内存page,kswapd还会周期性唤醒来执行内存操作。

common/drivers/staging/android/lowmemorykiller.c

static struct shrinker lowmem_shrinker = {
    .scan_objects = lowmem_scan,
    .count_objects = lowmem_count,
    .seeks = DEFAULT_SEEKS * 16
};

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

static void __exit lowmem_exit(void) {
    unregister_shrinker(&lowmem_shrinker);
}
static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
{
    struct task_struct *tsk;
    struct task_struct *selected = NULL;
    unsigned long 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数组找出相应的最小adj值
    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;
        }
    }

    if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
        return 0;
    }
    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;
        //小于目标adj的进程,则忽略
        if (oom_score_adj < min_score_adj) {
            task_unlock(p);
            continue;
        }
        //获取的是进程的Resident Set Size,也就是进程独占内存 + 共享库大小。
        tasksize = get_mm_rss(p->mm);
        task_unlock(p);
        if (tasksize <= 0)
            continue;

        //算法关键,选择oom_score_adj最大的进程中,并且rss内存最大的进程.
        if (selected) {
            if (oom_score_adj < selected_oom_score_adj)
                continue;
            if (oom_score_adj == selected_oom_score_adj &&
                tasksize <= selected_tasksize)
                continue;
        }
        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);
        //输出kill的log
        lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \ ...);

        lowmem_deathpending_timeout = jiffies + HZ;
        set_tsk_thread_flag(selected, TIF_MEMDIE);
        //向选中的目标进程发送signal 9来杀掉目标进程
        send_sig(SIGKILL, selected, 0);
        rem += selected_tasksize;
    }
    rcu_read_unlock();
    return rem;
}

参考:
LowMemoryKiller原理
Android进程优先级ADJ算法
Android LowMemoryKiller原理分析
Android 内存管理之LowMemoryKiller实现原理分析

上一篇:根据后台传的分数,显示星星的个数


下一篇:【SQL】SQL中简单的行转列题解