Android内存管理机制详解

windows  内存区别

与Windows

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

android内存的意义

android

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

 

进程管理软件

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

Android进程种类

1.       前台进程(foreground

        目前正在屏幕上显示的进程和一些系统进程。举例来说,DialerStorageGoogle 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

[cpp]  view plain copy
  1. module_init(lowmem_init);  
  2.   
  3. module_exit(lowmem_exit);  

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

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

[cpp]  view plain copy
  1. for (i = 0; i < array_size; i++) {  
  2.         if (other_file < lowmem_minfree[i]) {  
  3.             min_adj = lowmem_adj[i];  
  4.             break;  
  5.         }  
  6.     }  

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

[cpp]  view plain copy
  1. if (nr_to_scan <= 0 || min_adj == OOM_ADJUST_MAX + 1) {  
  2.        lowmem_print(5, "lowmem_shrink %d, %x, return %d\n",  
  3.                 nr_to_scan, gfp_mask, rem);  
  4.        return rem;  
  5.    }  

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

[cpp]  view plain copy
  1. for_each_process(p) {  
  2.         struct mm_struct *mm;  
  3.         struct signal_struct *sig;  
  4.         int oom_adj;  
  5.         task_lock(p);  
  6.         mm = p->mm;  
  7.         sig = p->signal;  
  8.         if (!mm || !sig) {  
  9.             task_unlock(p);  
  10.             continue;  
  11.         }  
  12.         oom_adj = sig->oom_adj;  
  13.         if (oom_adj < min_adj) {  
  14.             task_unlock(p);  
  15.             continue;  
  16.         }  
  17.         tasksize = get_mm_rss(mm);  
  18.         task_unlock(p);  
  19.         if (tasksize <= 0)  
  20.             continue;  
  21.         if (selected) {  
  22.             if (oom_adj < selected_oom_adj)  
  23.                 continue;  
  24.             if (oom_adj == selected_oom_adj &&  
  25.                 tasksize <= selected_tasksize)  
  26.                 continue;  
  27.         }  
  28.         selected = p;  
  29.         selected_tasksize = tasksize;  
  30.         selected_oom_adj = oom_adj;  
  31.         lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",  
  32.                  p->pid, p->comm, oom_adj, tasksize);  
  33.     }  

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

[cpp]  view plain copy
  1. if (selected) {  
  2.         if (fatal_signal_pending(selected)) {  
  3.             pr_warning("process %d is suffering a slow death\n",  
  4.                    selected->pid);  
  5.             read_unlock(&tasklist_lock);  
  6.             return rem;  
  7.         }  
  8.         lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",  
  9.                  selected->pid, selected->comm,  
  10.                  selected_oom_adj, selected_tasksize);  
  11.         force_sig(SIGKILL, selected);  
  12.         rem -= selected_tasksize;  
  13.     }  

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

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

资源配置

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

[plain]  view plain copy
  1. # Write value must be consistent with the above properties.  
  2.   
  3.    write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15  
  4.    write /proc/sys/vm/overcommit_memory 1  
  5.    write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144  
  6.   
  7.    class_start default  

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

[plain]  view plain copy
  1. # Set init its forked children's oom_adj.  
  2.   
  3.   write /proc/1/oom_adj -16  

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

[java]  view plain copy
  1. static final int EMPTY_APP_ADJ;  
  2. static final int HIDDEN_APP_MAX_ADJ;  
  3. static final int HIDDEN_APP_MIN_ADJ;  
  4. static final int HOME_APP_ADJ;  
  5. static final int BACKUP_APP_ADJ;  
  6. static final int SECONDARY_SERVER_ADJ;  
  7. static final int HEAVY_WEIGHT_APP_ADJ;  
  8. static final int PERCEPTIBLE_APP_ADJ;  
  9. static final int VISIBLE_APP_ADJ;  
  10. static final int FOREGROUND_APP_ADJ;  
  11. static final int CORE_SERVER_ADJ = -12;  
  12. static final int SYSTEM_ADJ = -16;<span style="font-family:Calibri;font-size:14px;"></span>  

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

在init.rc中: 

[plain]  view plain copy
  1. # Define the oom_adj values for the classes of processes that can be  
  2. # killed by the kernel.  These are used in ActivityManagerService.  
  3.     setprop ro.FOREGROUND_APP_ADJ 0  
  4.     setprop ro.VISIBLE_APP_ADJ 1  
  5.     setprop ro.HOME_APP_ADJ 1  
  6.     setprop ro.PERCEPTIBLE_APP_ADJ 2  
  7.     setprop ro.HEAVY_WEIGHT_APP_ADJ 3  
  8.     setprop ro.SECONDARY_SERVER_ADJ 4  
  9.     setprop ro.BACKUP_APP_ADJ 5  
  10.     setprop ro.HIDDEN_APP_MIN_ADJ 7  
  11.     setprop ro.EMPTY_APP_ADJ 15  
  12.   
  13. # Define the memory thresholds at which the above process classes will  
  14. # be killed.  These numbers are in pages (4k).  
  15.     setprop ro.FOREGROUND_APP_MEM 2048  
  16.     setprop ro.VISIBLE_APP_MEM 3072  
  17.     setprop ro.HOME_APP_MEM 3072  
  18.     setprop ro.PERCEPTIBLE_APP_MEM 4096  
  19.     setprop ro.HEAVY_WEIGHT_APP_MEM 4096  
  20.     setprop ro.SECONDARY_SERVER_MEM 10240  
  21.     setprop ro.BACKUP_APP_MEM 10240  
  22.     setprop ro.HIDDEN_APP_MEM 10240  
  23.     setprop ro.EMPTY_APP_MEM 14336  
  24.   
  25. # Write value must be consistent with the above properties.  
  26. # Note that the driver only supports 6 slots, so we have combined some of  
  27. # the classes into the same memory level; the associated processes of higher  
  28. # classes will still be killed first.  
  29.     write /sys/module/lowmemorykiller/parameters/adj 0,1,2,4,7,15  
  30.   
  31.     write /proc/sys/vm/overcommit_memory 1  
  32.     write /proc/sys/vm/min_free_order_shift 4  
  33.   write /sys/module/lowmemorykiller/parameters/minfree 2048,3072,4096,10240,10240,14336  
  34.   
  35.     # Set init its forked children's oom_adj.  
  36.     write /proc/1/oom_adj -16  

ActivityManagerService.java

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

[java]  view plain copy
  1. final boolean updateOomAdjLocked() {  
  2.        boolean didOomAdj = true;  
  3.        final ActivityRecord TOP_ACT = resumedAppLocked();  
  4.        final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;  
  5.   
  6.        if (false) {  
  7.            RuntimeException e = new RuntimeException();  
  8.            e.fillInStackTrace();  
  9.            Slog.i(TAG, "updateOomAdj: top=" + TOP_ACT, e);  
  10.        }  
  11.   
  12.        mAdjSeq++;  
  13.   
  14.        // Let's determine how many processes we have running vs.  
  15.        // how many slots we have for background processes; we may want  
  16.        // to put multiple processes in a slot of there are enough of  
  17.        // them.  
  18.        int numSlots = HIDDEN_APP_MAX_ADJ - HIDDEN_APP_MIN_ADJ + 1;  
  19.        int factor = (mLruProcesses.size()-4)/numSlots;  
  20.        if (factor < 1) factor = 1;  
  21.        int step = 0;  
  22.        int numHidden = 0;  
  23.          
  24.        // First try updating the OOM adjustment for each of the  
  25.        // application processes based on their current state.  
  26.        int i = mLruProcesses.size();  
  27.        int curHiddenAdj = HIDDEN_APP_MIN_ADJ;  
  28.  while (i > 0) {  
  29.            i--;  
  30.            ProcessRecord app = mLruProcesses.get(i);  
  31.            //Slog.i(TAG, "OOM " + app + ": cur hidden=" + curHiddenAdj);  
  32.            if (updateOomAdjLocked(app, curHiddenAdj, TOP_APP)) {  
  33.                if (curHiddenAdj < EMPTY_APP_ADJ  
  34.                    && app.curAdj == curHiddenAdj) {  
  35.                    step++;  
  36.                    if (step >= factor) {  
  37.                        step = 0;  
  38.                        curHiddenAdj++;  
  39.                    }  
  40.                }  
  41.                if (app.curAdj >= HIDDEN_APP_MIN_ADJ) {  
  42.                    if (!app.killedBackground) {  
  43.                        numHidden++;  
  44.                        if (numHidden > MAX_HIDDEN_APPS) {  
  45.                            Slog.e(TAG, "No longer want " + app.processName  
  46.                                    + " (pid " + app.pid + "): hidden #" + numHidden);  
  47.                            EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,  
  48.                                    app.processName, app.setAdj, "too many background");  
  49.                            app.killedBackground = true;  
  50.                            Process.killProcessQuiet(app.pid);  
  51.                        }  
  52.                    }  
  53.                }  
  54.            } else {  
  55.                didOomAdj = false;  
  56.            }  
  57.        }  
  58.        // If we return false, we will fall back on killing processes to  
  59.        // have a fixed limit.  Do this if a limit has been requested; else  
  60.        // only return false if one of the adjustments failed.  
  61.        return ENFORCE_PROCESS_LIMIT || mProcessLimit > 0 ? false : didOomAdj;  
  62.    }<span style="font-family:Calibri;font-size:14px;"> </span>  

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


基于以上几个问题,不难看出,我们修改的目标也将非常明确,主要解决两个矛盾:

 

拉开各进程的阀值层次,使得进程管理机制能更有效得工作

 

提升阀值上限,空出更多的空余内存,以提升系统整体的运行速度

 

进程管理策略设置原则:

 

前台进程、可见进程和次要服务是与用户体验息息相关的内容,这部分的进程管理策略要相

对保守,给这些进程留下足够的运行空间

 

压榨无用进程,腾出内存空间给主要程序使用

 

下面笔者总结了几种设置方式,适应不同的使用需要:

 

游戏玩家/重度浏览器使用者配置:

 

用户特点:长时间专注于某一特定的,高内存需求的程序,对多任务的需求不高

 

配置参数:

 

1.Foreground:6

 

2.Visible:8

 

3.Secondary

 

Server :16

 

4.Hiden App :80

 

5.Content

 

Provider:90


----------------------- Page 7-----------------------

6.Empty:100

 

配置理念:压榨后台进程,内容供应节点和空进程,将内存尽可能多得留给前台进程和系统,

提升游戏速度和浏览器体验

 

优点:程序启动和运行的速度最快

 

缺点:多任务处理不理想,开启程序较多时,后台进程会被终止

 

多任务配置:

 

用户特点:同时运行多个应用程序,需要经常在多个程序间切换

 

配置参数:

 

1.Foreground:6

 

2.Visible:8

 

3.Secondary

 

Server :16

 

4.Hiden App :20

 

5.Content

 

Provider:60

 

6.Empty:100

 

配置理念:压榨空进程,给内容供应节点留有一定空间,最大限度提升后台程序的使用空间,

提升多任务的处理能力

 

优点:运行多个程序时,由于可支配内存较多,后台程序不容易被终止

 

缺点:程序启动的速度和整体系统的运行速度可能会比游戏玩家配置略慢一些,由于经常运

行多任务,平时系统的响应速度会受到一定影响

 

轻度用户/女生专用配置

 

用户特点:手机的主要功能是短信和电话,偶尔用用相机自拍

 

配置参数:


----------------------- Page 8-----------------------

1.Foreground:6

 

2.Visible:8

 

3.Secondary

 

Server :16

 

4.Hiden App :24

 

5.Content

 

Provider:32

 

6.Empty:48

 

配置理念:压榨空进程,给内容供应节点留有一定空间,最大限度提升后台程序的使用空间,

提升多任务的处理能力

 

优点:比较均衡的配置,提升了系统的可用内存,使得系统的整体速度得到了提高,拉开了

各级进程的管理策略层次,使得管理机制更有效率

 

缺点:比较均衡的配置,无明显缺点

 

以上内容主要是转发,希望对大家了解android 的内存管理机制有帮助


你可能感兴趣的:(Android内存管理机制详解)