Android 内存管理机制详解

??嵌入式设备的一个普遍特点是内存容量相对有限。当运行的程序超过一定数量时,或者涉及复杂的计算时,很可能出现内存不足,进而导致系统卡顿的现象。Android 系统也不例外,它同样面临着设备物理内存短缺的困境。对于已经启动过一次的Android程序,再一次启动所花的时间会明显减少。原因在于Android系统并不马上清理那些已经”淡出视野”的程序(比如你调用Activity.finish退出UI界面)。它们在一定的时间里仍然驻留在内存中。这样做的好处是明显的,即下一次启动不需要再为程序重新创建一个进程;坏处就是,加大了内存OOM的概率。

Linux内存监控机制(OOMKiller)

??Android是基于Linux的,而Linux底层内核有自己的内存监控机制,即OOMKiller。一旦发现系统的可用内存达到临界值,这个OOM的管理者就会自动跳出来清理内存。

OOMKiller有不同的策略和不同的处理手段。它的核心思想如下:

按照优先级顺序,从低到高逐步杀掉进程,回收内存。

优先级的设定策略主要考虑两个方面:一个是要考虑对系统的损害程度(例如系统的核心进程,优先级通常较高),另一方面也希望尽可能多地释放无用内存。一个合理的策略至少需要考虑以下几个因素:

  • 进程消耗的内存
  • 进程占用的CPU时间
  • oom_adj(OOM权重)

??内核所管理的进程都有一个衡量其oom权重的值,存储在/proc/< PID >/oom_adj中。根据这一权重值以及上面所提及的若干其他因素,系统会实时给每个进程评分,以决定OOM时应该杀死哪些进程。

这个值存储在/proc/< PID >/oom_score中。

oom_score分数越低的进程,被杀死的概率越小,或者说被杀死的时间越晚。

下面展示了PID为5912的NetworkManager进程的oom_adj 和oom_score,可以看到分数很低,说明此进程十分重要,一般不会被系统杀死。

Android 内存管理机制

基于Linux内核OOM Killer的核心思想,Android 系统扩展出了自己的内存监控体系。因为Linux下的内存杀手需要等到系统资源”濒临绝境”的情况下才会产生效果,而Android则实现了自己的Killer.

Android 系统为此开发了一个专门的驱动,名为Low Memory Killer(LMK)。源码路径在内核工程的 drivers/staging/android/Lowmemorykiller.c中。

它的驱动加载函数如下:

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

可见LMK向内核线程注册了一个shrinker的监听回调,实现体为lowmem_shrinker。当系统的空闲页面低于一定阈值时,这个回调就会被执行。

Lowmemorykiller.c 中定义了两个数组,分别如下:

static short lowmem_adj[6] = {
    0,
    1,
    6,
   12,
 };
static int lowmem_adj_size = 4;//下面的数值以此为单位(页大小)
static int lowmem_minfree[6] = {
    3 * 512,    /* 6MB */
    2 * 1024,   /* 8MB */
    4 * 1024,   /* 16MB */
    16 * 1024,  /* 64MB */
};

第一个数组lowmem_adj最多有6个元素,默认只定义了4个,它表示可用容量处于”某层级”时需要被处理的adj值;第二个数组则是对”层级”的描述。这样说可能不清楚,举个例子,lowmem_minfree 的第一个元素是3*512,3*512*lowmem_adj_size=6MB.意味着当可用内存小于6MB时,Killer需要清理adj的值为0(即lowmem_adj的第一个元素)以下的那些进程。其中adj的取值范围是-17~15,数字越小表示进程级别越高,通常只有0-15被使用。

下图是LWK机制的实现简图。

这两个数组只是系统的预定义值,我们可以根据项目的实际需求来定制。

Android系统提供了相应的文件来供我们修改这两组值。

路径如下:

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

可以在 init.rc(系统启动时,由init进程解析的第一个脚本)中加入如下语句。init.rc路径为/system/core/rootdir/init.rc

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

Android进程分类

进程omm_adj的大小跟进程的类型以及进程被调度的次序有关。

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

在AMS 用于处理进程的相关代码文件ProcessList.java 中,定义了不同类型的adj值,如下所示:

/**
*省略其它代码
*/
    //未知的adj
    static final int UNKNOWN_ADJ = 16;
    static final int CACHED_APP_MAX_ADJ = 15;
    static final int CACHED_APP_MIN_ADJ = 9;
    //B list of service ,和A list相比,对用户黏合度小一些
    static final int SERVICE_B_ADJ = 8;
    //用户前一次交互的进程
    static final int PREVIOUS_APP_ADJ = 7;
    //Launcher进程
    static final int HOME_APP_ADJ = 6;
    //运行了application service的进程
    static final int SERVICE_ADJ = 5;
    //重量级应用程序进程
    static final int HEAVY_WEIGHT_APP_ADJ = 4;
    //用于承载backup相关操作的进程
    static final int BACKUP_APP_ADJ = 3;
    //这类进程能被用户感觉到但不可见,如后台运行的音乐播放器
    static final int PERCEPTIBLE_APP_ADJ = 2;
    //有前台可见的Activity进程,如果轻易杀死这类进程,将严重影响用户体验
    static final int VISIBLE_APP_ADJ = 1;
    //当前正在运行的那个进程,即用户正在交互的程序
    static final int FOREGROUND_APP_ADJ = 0;
    static final int PERSISTENT_SERVICE_ADJ = -11;
    //persistent性质的进程,如telephony
    static final int PERSISTENT_PROC_ADJ = -12;
    //系统进程
    static final int SYSTEM_ADJ = -16;

/**
*省略其它代码
*/

上面定义的adj数值来看,adj越小表示进程类型就越重要,特别的,系统进程的默认oom_adj 为-16,这类进程永远不会被杀死。

还需要注意的是updateOomLevels函数,内部原理是通过写上面两个文件来实现,AMS 会根据系统的当前配置自动修正adj和minfree,以尽可能适配不同的硬件。函数源码如下所示:

private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
        // Scale buckets from avail memory: at 300MB we use the lowest values to
        // 700MB or more for the top values.
        float scaleMem = ((float)(mTotalMemMb-300))/(700-300);

        // Scale buckets from screen size.
        int minSize = 480*800;  //  384000
        int maxSize = 1280*800; // 1024000  230400 870400  .264
        float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
        if (false) {
            Slog.i("XXXXXX", "scaleMem=" + scaleMem);
            Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth
                    + " dh=" + displayHeight);
        }

        float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
        if (scale < 0) scale = 0;
        else if (scale > 1) scale = 1;
        int minfree_adj = Resources.getSystem().getInteger(
                com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
        int minfree_abs = Resources.getSystem().getInteger(
                com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
        if (false) {
            Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs);
        }

        // We‘ve now baked in the increase to the basic oom values above, since
        // they seem to be useful more generally for devices that are tight on
        // memory than just for 64 bit.  This should probably have some more
        // tuning done, so not deleting it quite yet...
        final boolean is64bit = false; //Build.SUPPORTED_64_BIT_ABIS.length > 0;

        for (int i=0; i<mOomAdj.length; i++) {
            int low = mOomMinFreeLow[i];
            int high = mOomMinFreeHigh[i];
            mOomMinFree[i] = (int)(low + ((high-low)*scale));
            if (is64bit) {
                // On 64 bit devices, we consume more baseline RAM, because 64 bit is cool!
                // To avoid being all pagey and stuff, scale up the memory levels to
                // give us some breathing room.
                mOomMinFree[i] = (3*mOomMinFree[i])/2;
            }
        }

        if (minfree_abs >= 0) {
            for (int i=0; i<mOomAdj.length; i++) {
                mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]
                        / mOomMinFree[mOomAdj.length - 1]);
            }
        }

        if (minfree_adj != 0) {
            for (int i=0; i<mOomAdj.length; i++) {
                mOomMinFree[i] += (int)((float)minfree_adj * mOomMinFree[i]
                        / mOomMinFree[mOomAdj.length - 1]);
                if (mOomMinFree[i] < 0) {
                    mOomMinFree[i] = 0;
                }
            }
        }

        // The maximum size we will restore a process from cached to background, when under
        // memory duress, is 1/3 the size we have reserved for kernel caches and other overhead
        // before killing background processes.
        mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024) / 3;

        // Ask the kernel to try to keep enough memory free to allocate 3 full
        // screen 32bpp buffers without entering direct reclaim.
        int reserve = displayWidth * displayHeight * 4 * 3 / 1024;
        int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust);
        int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute);

        if (reserve_abs >= 0) {
            reserve = reserve_abs;
        }

        if (reserve_adj != 0) {
            reserve += reserve_adj;
            if (reserve < 0) {
                reserve = 0;
            }
        }

        if (write) {
            ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
            buf.putInt(LMK_TARGET);
            for (int i=0; i<mOomAdj.length; i++) {
                buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
                buf.putInt(mOomAdj[i]);
            }

            writeLmkd(buf);
            SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
        }
        // GB: 2048,3072,4096,6144,7168,8192
        // HC: 8192,10240,12288,14336,16384,20480
    }

改变进程权重adj值

除了系统的评定标准,我们也可以用自己的方式来改变进程的权重值。

1.写文件

??和上述提到的adj和minfree类似,进程的oom_adj也可以通过写文件的形式来修改,路径为/proc/< PID >/oom_adj。比如init.rc中有如下语句

on early-init
 # Set init and its forked children‘s oom_adj.
 write /proc/1/oom_adj -16

PID 为1的进程为init程序,将此进程的oom_adj 设置为-16,保证它不会被杀死。

2.设置android:persistent

??对某些非常重要的程序,不希望它被系统杀死。在AndroidMainifest.xml文件中给application 标签添加”android:persistent=true”属性,将应用程序设置为常驻内存,但需要特别注意,如果应用程序本不够完善,而系统又不能正常回收,那么会导致意想不到的问题。



参考资料

《深入理解Android内核设计思想》

时间: 2024-10-03 23:17:33

Android 内存管理机制详解的相关文章

Android内存管理机制详解 (zhuan)

http://www.2cto.com/kf/201212/175786.html 与windows内存区别 在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这方面,区别于 Windows的内存管理.主要特点是,无论物理内存有多大,Linux都将其充份利用,将一些程序调用过的硬盘数据读入内存,利用内存读写的高速特性来提高Linux系统的数据访问性能.而Windows是只在需要内存时,才为应用程序分配内存,

memcached内存管理机制详解

    我们知道,memcached是一个内存缓存系统,因此对于内存的管理是需要使用者了解的.本文将对memcached的内存模型及管理机制做一个详细的描述. 基本概念 在开始之前,有必要先了解几个基本概念: 1.slab class:在memcached中,对元素的管理是以slab为单元进行管理的.每个slab class对应一个或多个空间大小相同的chunk.参考下图一. 2.chunk:存放元素的最小单元.用户数据item(key.value等)最终会保存在chunk中.memcach

ARC内存管理机制详解

ARC在OC里面个人感觉又是一个高大上的牛词,在前面Objective-C中的内存管理部分提到了ARC内存管理机制,ARC是Automatic Reference Counting---自动引用计数.有自动引用计数,那么就得有手动引用计数MRC(Mannul Reference Counting),前面已经提到过了MRC.那么在ARC模式下是不是意味着我们就可以一点也不用进行内存管理的呢?并不是这样的,我们还需要代码进行内存的管理.下面会结合着代码把OC中的ARC机制做一个详细的总结(欢迎大家批

Android事件分发机制详解(1)----探究View的事件分发

探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d("TAG", "onClick execute"); } }); 如果在需要一个触摸事件 [java] view plaincopy button.setO

Android事件分发机制详解(2)----分析ViewGruop的事件分发

首先,我们需要 知道什么是ViewGroup,它和普通的View有什么区别? ViewGroup就是一组View的集合,它包含很多子View和ViewGroup,是Android 所有布局的父类或间接父类. 但ViewGroup也是一个View,只不过比起View,它可以包含子View和定义布局参数的功能. 现在,通过一个Demo演示Android中ViewGroup的事件分发机制. 首先我们来自定义一个布局,命名为MyLayout,继承自LinearLayout,如下 所示: public c

Android事件传递机制详解及最新源码分析——ViewGroup篇

在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴,强烈建议先阅读上一篇. 好了,废话还是少说,直奔主题,开始本篇的ViewGroup事件传递机制探索之旅. 依然从简单的Demo例子现象开始分析 新建安卓工程,首先自定义一个Button以及一个RelativeLayout,很简单,只是重写了主要与事件传递机制相关的方法,代码如下: 自定义WLButton类: 1 public class WLButton e

Android 接口回调机制详解

在使用接口回调的时候发现了一个经常犯的错误,就是回调函数里面的实现有可能是用多线程或者是异步任务去做的,这就会导致我们期望函数回调完毕去返回一个主函数的结果,实际发现是行不通的,因为如果回调是多线程的话你是无法和主函数同步的,也就是返回的数据是错误的,这是非常隐秘的一个错误.那有什么好的方法去实现数据的线性传递呢?先介绍下回调机制原理. 回调函数 回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数

SAP 内存管理参数详解

ST02,这里是内存管理参数详解 初学SAP看到很内存管理参数会有一头雾水,不知如何解读,这里之接上货 这只是一个例子,实际设中,我们会通常直接设定以下: rdisp/roll_maxfs=125000 rdisp/roll_shm=125000 rdisp/PG_shm=312500 rdisp/PG_maxfs=312500 原文地址:https://www.cnblogs.com/tingxin/p/11958011.html

Android内存管理机制及优化

1.基于Linux内存管理 Android系统是基于Linux 2.6内核开发的开源操作系统,而linux系统的内存管理有其独特的动态存储管理机制.不过Android系统对Linux的内存管理机制进行了优化,Linux系统会在进程活动停止后就结束该进程,而Android把这些进程都保留在内存中,直到系统需要更多内存为止.这些保留在内存中的进程通常情况下不会影响整体系统的运行速度,并且当用户再次激活这些进程时,提升了进程的启动速度. 2.Android内存分配机制 与java的垃圾回收机制类似,系