Android系统内存管理

2019-11-10  本文已影响0人  ArcherZang

部分内容出至林学森的Android内核设计思想。
Android官网内存管理
部分出至简书https://www.jianshu.com/p/94d1cd553c44

Android本质是Linux所以先从Linux说起。

Linux

Linux的内存管理为系统中所有的task提供可靠的内存分配、释放和保护机制。
核心:
虚拟内存
内存分配与释放
内存保护

虚拟内存思想:

将外存储器的部分空间作为内存的扩展,如从硬盘划出4GB大小。
当内存资源不足时,系统按照一定算法自动条形优先级低的数据块,并把他们存储到硬盘中。
后续如果需要用到硬盘中的这些数据块,系统将产生“缺页”指令,然后把他们交换回内存中。
这些都是由操作系统内核自动完成的,对上层应用”完全透明“。

虚拟内存机制:
  1. 逻辑地址(Logical Address,相对地址)
    是程序编译后产生的地址,两部分组成:段选择子(Segment Selector)、偏移值(Offset)
  1. 线性地址
    线性地址是逻辑地址经过分段机制转换后形成的;由页基址和偏移地址(页内偏移量)组成。
    思想:根据段选择子中的TL自动,得知段描述的存储,经过GDTR或者LDTR获得GDT或者LDT的存储地址。根据段选择子中的INDEX字段,到GDT或者LDT中查找到对应的段描述符。根据段描述符获得此段的基地址。由基地址+段内偏移值得到线性地址。
  2. 物理地址
    机器真实的物理内存地址空间范围。
    比如64KB内存的系统,物理地址范围是0x0000~0xFFFF。
  3. 段页式内存管理
    逻辑地址——>分段机制转换——>线性地址——>分页机制是否开启——>分页机制转换——>物理地址
    不是所有系统都支持段页式内存管理。有些系统只提供页式管理机制,Linux内存理论上是段页式,也只实现了分页机制(分段机制只用到了一部分功能)。
    页:分页机制的操作对象,是固定大小的内存块。一般情况下4KB大小。
    页框:对物理内存的最小操作单位。页和页框的大小必须完全一致。
  4. 虚拟内存
    当前与物理内存没有映射关系的页,访问时回产生缺页中断,操作系统自动介入处理,利用一定的算法将当前不常用的页调出内存,从而为缺失页腾出位置,然后将缺失页从外存储器重新取回,最后返回中断点继续操作。
内存保护:

每个进程的逻辑地址和物理地址都不是直接对应的,任何进程都没办法访问到它管辖范围外的内存空间——即刻意产生的内存越界与非法访问,操作系统也会马上阻止并强行关闭程序,从而有力的保障应用程序和操作系统的安全和稳定。

内存分配与回收
  1. 保证硬件无关性
    每个硬件平台的物理内存型号、大小甚至架构(比如不同的体系结构)等都可能是不一样的。要尽可能实现向上的“透明”。
  2. 动态分配内存和回收
    如何为内存划分不同的使用区域:分配的粒度问题,即分配 的最小单位;如何管理和区别已使用和未使用的内存;如何回收和再利用。
    Native(C/C++):栈上直接分配出栈后自动释放,堆上分配函和释放数包括malloc/free、new/delete。
    Java:java堆,方法区(包含运行时常量池),java虚拟机栈、本地方法栈这些都会涉及内存分配。垃圾回收器负责回收释放内存,主要是java堆和方法区;java虚拟机栈和本地方法栈基本是出栈后释放,也取决于虚拟机的具体实现。
  3. 内存碎片
    6块内存,连续使用了5块,其中第二块欸回收释放后;就会形成两个不连续的内存块。
Linux OOMKiller即内存监控机制

一旦发现系统的可用内存达到临界值,机会按照优先级顺序,匆匆低到高逐步杀掉进程,回收内存。
存储位置:/proc/<PID>/oom_score
优先级策略:
进程消耗的内存
进程占用的CPU时间
oom_adj(OOM权重)

Android

Android平台运行的前提是可用内存是浪费的内存。它试图在任何时候使用所有可用的内存。例如,系统会在APP关闭后将其保存在内存中,以便用户可以快速切换回它们。出于这个原因,Android设备通常运行时只有很少的空闲内存。在重要系统进程和许多用户应用程序之间正确分配内存内对存管理是至关重要。
Android有两种主要的机制来处理低内存的情况:内核交换守护进程(kernel swap daemon)和低内存杀手(low-memory killer)。

切换APP

当用户在APP之间切换时,Android会在最近使用的(LRU)缓存中保留不在前台的APP,即用户看不到的APP,或运行类似音乐播放的前台服务。如果用户稍后返回APP,系统将重用该进程,从而使APP切换更快。
如果你的APP有一个缓存进程,并且它保留了当前不需要的内存,那么即使用户不使用它,你的APP也会影响系统的整体性能。由于系统内存不足,它会从最近使用最少的进程开始杀死LRU缓存中的进程。该系统还负责处理占用最多内存的进程,并可以终止这些进程以释放RAM。
当系统开始终止LRU缓存中的进程时,它主要是自底向上工作的。系统还考虑哪些进程消耗更多的内存,从而在终止时为系统提供更多的内存增益。你在LRU列表中消耗的内存越少,你就越有可能留在列表中并能够快速恢复。

内存共享(Share memory)

为了满足RAM的所有需求,Android尝试共享RAM来跨进程通信。它可以做到以下方式:

内存类型

Android设备包含三种不同类型的内存:RAM、zRAM和storage。
注意:CPU和GPU都访问同一个RAM。

内存页(Memory Pages)

内存被拆分成页。通常每页有4KB的内存。
页面被认为是空闲的或已使用的。
空闲页是未使用的RAM。
已使用页是系统正在积极使用的RAM,分为以下类别:

干净的页面(Clean pages)包含一个文件(或文件的一部分)的一份精确副本存在存储器上。当一个干净的页面不再包含一个精确的文件副本(例如,来自应用程序操作的结果)时,它就变成了脏页。可以删除干净的页,因为它们始终可以使用存储中的数据重新生成;不能删除脏页(Dirty pages),否则数据将丢失。

计算内存占用

内核跟踪系统中的所有内存页。


Pages used by different processes

当确定一个应用程序正在使用多少内存时,系统必须考虑shared pages。APP访问相同的服务或库将可能共享内存页。例如,Google Play Services 和一个游戏APP可能共享一个位置服务。这使得很难确定有多少内存属于这个服务相对于每个APP。


Pages shared by two apps (middle)
要确定APP的内存占用,可以使用以下指标:

当操作系统想要知道所有进程使用了多少内存时,PSS非常有用,因为页面不会被多次计数。PSS需要很长时间来计算,因为系统需要确定哪些页面是共享的,以及被有多少进程。RSS不区分共享页面和非共享页面(使计算速度更快),更适合于跟踪内存分配的更改。

内核交换守护程序(kernel swap daemon)

内核交换守护进程(kswapd)是Linux内核的一部分,它将使用过的内存转换为空闲内存。当设备上的空闲内存不足时,守护进程将变为活动状态。Linux内核保持低和高的可用内存阈值。当空闲内存低于低阈值时,kswapd开始回收内存。当空闲内存达到高阈值,kswapd将停止回收内存。
kswapd可以通过删除干净的页面来回收干净的页面,因为它们有存储器支持并且没有被修改。如果进程试图寻址已删除的干净页,则系统会将该页从存储器复制到RAM。此操作称为请求分页。


Clean page, backed by storage, deleted

kswapd将缓存的私有脏页(private dirty pages)和匿名脏页(anonymous dirty pages)移动到zRAM进行压缩。这样做可以释放RAM中的可用内存(空闲页)。如果进程试图触摸zRAM中脏页,则该页将被解压缩并移回RAM。如果与压缩页关联的进程被终止,则该页将从zRAM中删除。
如果可用内存量低于某个阈值,系统将开始终止进程。


Dirty page moved to zRAM and compressed

lmkd实现源码要在system/core/lmkd/lmkd.c。
lmkd会创建名为lmkd的socket,节点位于/dev/socket/lmkd,该socket用于跟上层framework交互。

service lmkd /system/bin/lmkd
    class core
    critical
    socket lmkd seqpacket 0660 system system
    writepid /dev/cpuset/system-background/tasks 

小结:
LMK_TARGET:AMS.updateConfiguration()的过程中调用updateOomLevels()方法, 分别向/sys/module/lowmemorykiller/parameters目录下的minfree和adj节点写入相应信息;
LMK_PROCPRIO:AMS.applyOomAdjLocked()的过程中调用setOomAdj()向/proc/<pid>/oom_score_adj写入oom_score_adj后直接返回;
LMK_PROCREMOVE:AMS.handleAppDiedLocked或者 AMS.cleanUpApplicationRecordLocked()的过程,调用remove(),目前不做任何事,直接返回;

onTrimMemory()

为了进一步帮助平衡系统内存并避免终止APP进程,可以Activity类中实现ComponentCallbacks2接口。提供的onTrimMemory()回调方法允许APP在前台或后台侦听与内存相关的事件,然后释放对象以响应应用程序生命周期或表明系统需要回收内存的系统事件。
onTrimMemory()回调是在Android 4.0(API级别14)中添加的。
对于早期版本,可以使用onLowMemory(),它大致相当于TRIM_MEMORY_COMPLETE事件。

import android.content.ComponentCallbacks2;
// Other import statements ...

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code ...

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that was raised.
     */
    public void onTrimMemory(int level) {

        // Determine which lifecycle or system event was raised.
        switch (level) {
            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
                /*用户界面进入后台
                   Release any UI objects that currently hold memory.
                   The user interface has moved to the background.
                */
                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
                /* APP正在运行
                   Release any memory that your app doesn't need to run.
                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
                   begin killing background processes.
                */
                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
                /* APP在后台
                   Release as much memory as the process can.
                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process will be one of
                   the first to be terminated.
                */
                break;

            default:
                /* 一般消息释放非关键数据
                  Release any non-critical data structures.
                  The app received an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}
Low-memory killer

一个专门的驱动。(Linux Kernel 4.12 已移除交给kswapd处理)。
很多时候,kswapd无法为系统释放足够的内存。在这种情况下,系统使用onTrimMemory()通知APP内存不足,应该减少其分配。如果这还不够,内核将开始终止进程以释放内存,它使用低内存杀手(LMK)来完成这个任务。
为了决定要终止哪个进程,LMK使用一个名为oom_adj_score的“out of memory”分数来确定运行进程的优先级,高分的进程首先被终止。
后台应用程序首先被终止,系统进程最后被终止。
下表列出了从高到低的LMK评分类别。第一排得分最高的项目将首先被杀死:

Android processes, 高分在前,低分在后
以下是上表中各种类别的说明:
Background apps: 以前运行但当前未处于活动状态的APP。LMK将首先杀掉后台APP,从最高分的那个开始。
Previous app:最近使用的后台APP。前一个APP有更高的优先级(分数)比后台APP,因为用户更有可能切换到它,而不是后台APP中的一个。
Home app:这是launcher APP。杀死它会让墙纸消失。
Services:服务由APP启动,可能包括同步或上传到云。
Perceptible apps:在某种程度上用户可感知的非前台APP,如显示小用户界面的正在运行的搜索进程或正在收听音乐。
Foreground app:当前正在使用的APP。关闭前台APP看起来像是APP崩溃,可能会向用户表明设备出了问题。
Persistent (services):这些是设备的核心服务,如电话和wifi。
System:系统进程。当这些进程被终止时,手机可能会重新启动。
Native:系统使用的非常低级的进程(例如,kswapd)。
设备制造商可以改变LMK的行为。
如何修改:
  1. LowMemoryKiller 的阈值的设定
    主要保存在2个文件之中,分别是:
    /sys/module/lowmemorykiller/parameters/adj
    /sys/module/lowmemorykiller/parameters/minfree
    adj保存着当前系统杀进程的等级,minfree则是保存着对应的内存阀值。
    查询命令:

    shamu:/ # cat /sys/module/lowmemorykiller/parameters/adj
    shamu:/ # cat /sys/module/lowmemorykiller/parameters/minfree
    
    • framework的config.xml
      <integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">-1</integer>
      <integer name="config_lowMemoryKillerMinFreeKbytesAdjust">0</integer>
      config_....KbytesAbsolute:非-1的情况下,是绝对值, AN使用下面算法,得到实际数组值。

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

      config_....KbytesAdjust:非0情况下, 直接在每个数组值上 += reserve_adj;
      如: <integer name="config_....KbytesAdjust">-512</integer>,表明每个数组值都减少512。

    • 当然了在实际上开发过程中,也可以直接在这个函数里面打补丁,或者读取系统属性,通过属性来进行配置等等。 像MStar方案,就定义了两个属性来进行第三方的配置: ro.mstar.lmkd.minfree和ro.mstar.lmkd.adj

    • 在init.rc中通过下面的语句来修改:

      write /system/module/lowmemorykiller/parameters/adj 0,8
      write /system/module/lowmemorykiller/parameters/minfree 1024,4096
      
    • ActivityManagerService在运行时会根据系统的当前配置自动修正adj和minfree,尽可能适配不同的硬件。AMS内部updateOomLevels函数也是通过上述方法实现。

  2. Android进程所属adj值

    • 和前面类似通过改写文件的方式,如init.rc中将PID值为1的进程(init)的adj值改为-16,以保证它不会被杀死。
      on early-init
          write /proc/1/oom_adj-16
      
    • AndroidManifest.xml中给application标签添加“android:persistent=true”,设置为常驻内存。
  3. Linux和内核对应关系

    Android Version API Level Linux Version in AOSP Header Version
    1.5 Cupcake 3 (2.6.27)
    1.6 Donut 4 (2.6.29) 2.6.18
    2.0/1 Eclair 5-7 (2.6.29) 2.6.18
    2.2.x Froyo 8 (2.6.32) 2.6.18
    2.3.x Gingerbread 9, 10 (2.6.35) 2.6.18
    3.x.x Honeycomb 11-13 (2.6.36) 2.6.18
    4.0.x Ice Cream San 14, 15 (3.0.1) 2.6.18
    4.1.x Jelly Bean 16 (3.0.31) 2.6.18
    4.2.x Jelly Bean 17 (3.4.0) 2.6.18
    4.3 Jelly Bean 18 (3.4.39) 2.6.18
    4.4 Kit Kat 19, 20 (3.10) 2.6.18
    5.x Lollipop 21, 22 (3.16.1) 3.14.0
    6.0 Marshmallow 23 (3.18.10) 3.18.10
    7.0 Nougat 24 3.18.48 4.4.0 4.4.1
    7.1 Nougat 25 ? 4.4.1
    8.0 Oreo 26 3.18.72 4.4.83 4.9.44 4.10.0
    8.1 Oreo 27 3.18.70 4.4.88 4.9.56 4.10.0
    9.0 Pie 28 4.4.146 4.9.118 4.14.61 4.15.0
    10.0 Q 29 4.9.191 4.14.142 4.19.71 5.0.3
虚拟机

Android Runtime(ART)和Dalvik虚拟机使用分页(Paging)和内存映射(mmapping)来管理内存。应用程序通过分配新对象或触摸已映射页面来修改内存都将保留在RAM中,并且不能被调出。应用程序释放内存的唯一方式是垃圾收集器。

public void doSomethingMemoryIntensive() {
    // Before doing something that requires a lot of memory,
    // check to see whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work ...
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}
上一篇 下一篇

猜你喜欢

热点阅读