Android操作系统之内存回收策略
作者:mrr 发布时间:2021-09-26 06:38:01
Android 是一款基于 Linux 内核,面向移动终端的操作系统。为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使应用程序关闭但不退出,并由操作系统进行进程的回收管理。本文在 Application Framework 与 Linux 内核两个层次上,以进程为粒度,对 Android 操作系统的进程资源回收机制进行了剖析。读者可以从本文获得对 Android 应用程序的生存周期的进一步理解,从而更加合理、高效地构建应用程序。
Android 操作系统中的内存回收可分为两个层次:
1、默认内存回收、即Application Framework 层的默认回收。
2、内核级内存回收。
Linux 内核中的内存回收 lowmemorykiller、OOM_killer。
默认内存回收:(代码可查阅 ActivityManagerService.java类)回收动作入口activityIdleInternal()。
Android 系统中内存回收的触发点大致可分为三种情况。
第一,用户程序调用 StartActivity(), 使当前活动的 Activity 被覆盖;
第二,用户按 back 键,退出当前应用程序;第三,启动一个新的应用程序。
这些能够触发内存回收的事件最终调用的函数接口就是 activityIdleInternal()。当 ActivityManagerService 接收到异步消息 IDLE_TIMEOUT_MSG 或者 IDLE_NOW_MSG 时,activityIdleInternal() 将会被调用。 IDLE_NOW_MSG 由 Activity 的切换以及 Activiy 焦点的改变等事件引发,IDLE_TIMEOUT_MSG 在 Activity 启动超时的情况下引发,一般这个超时时间设为 10s,如果 10s 之内一个 Activity 依然没有成功启动,那么将发送异步消息 IDLE_TIMEOUT_MSG 进行资源回收。activityIdleInternal() 的主要任务是改变系统中 Activity 的状态信息,并将其添加到不同状态列表中。它的主要工如下:
首先,调用 scheduleAppGcsLocked() 方法通知所有进行中的任务进行垃圾回收。scheduleAppGcsLocked() 将进行调度 JVM 的 garbage collect,回收一部分内存空间,这里仅仅是通知每个进程自行进程垃圾检查并调度回收时间,而非同步回收。
然后,取出 mStoppingActivities 和 mFinishigActivities 列表中的所有内容,暂存在临时变量中。这两个列表分别存储了当前状态为 stop 和 finishi 的 activity 对象。对于 stop 列表,如果其中的 activity 的 finish 状态为 true,判断是不是要立即停止,如果要立即停止则调用 destroyActivityLocked() 通知目标进程调用 onDestroy() 方法,否则,先调用resumeTopActivity() 运行下一个 Activity。如果 finish 状态为 false,则调用 stopActivityLocked() 通知客户进程停止该 Activity,这种情况一般发生在调用 startActivity() 后。对于 finish 列表,直接调用 destroyActivityLocked() 通知客户进程销毁目标 Activity。这里的 destroyActivityLocked 等函数并没有真正意义上改变内存的使用,只是将其状态改变为“允许回收”,真正的回收在下面即将调用的 trimApplications() 函数中。
private final void trimApplications() {
synchronized (this) {
// First remove any unused application processes whose package
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
(1)//kill process;
}
if (!updateOomAdjLocked()) {
(2)//do something default
}
// Finally, if there are too many activities now running, try to
// finish as many as we can to get back down to the limit.
(3)do something
}
}
(1)当程序执行到 trimApplications() 之后,首先检查 mRemovedProcesses 列表中的进程。mRemovedProcesses 列表中主要包含了 crash 的进程、5 秒内没有响应并被用户选在强制关闭的进程、以及应用开发这调用 killBackgroundProcess 想要杀死的进程。调用 Process.killProcess 将所有此类进程全部杀死。
(2)调用 updateOomAdjLocked() 函数,若成功返回,说明 Linux 内核支持 setOomAdj() 接口,updateOomAdjLocked 将修改 adj 的值并通知 linux 内核,内核根据 adj 值以及内存使用情况动态管理进程资源(lowmemorykiller 和 oom_killer)。若 updateOomAdjLocked() 返回为假,则表示当前系统不支持 setOomAdj() 接口,将在本地进行默认的资源回收。
(3)最后,如果当前依然运行了过多的 Activity,对多余的 Activity 进行回收。 trimApplications() 的大多数的代码都在处理 Oom_killer 不存在情况下的默认资源回收,下面对其默认回收过程(即代码中标记(2)的位置)进行进一步分析。其回收过程可大致描述如下。
步骤一,获取当前所有运行的进程 mLruProcesses,mLruProcesses 中的排序规则是按最近使用时间。对 mLruProcesses 中不能被关闭的进程进行计数,这些不能被关闭的进程包括运行 service 的进程,运行broadcast receiver 的进程等。
步骤二, 设当前最大运行进程数 curMaxProcs = curMaxProcs + numServiceProcs(即默认最大进程数与运行 Service 的进程数之和),如果当前进程的数量 mRemovedProcesses.size() 大于这个值,则遍历所有当前运行的进程,杀死符合条件的那些进程并释放内存。进程被杀死的条件是:必须是非 persistent 进程,即非系统进程,必须是空进程,即进程中没有任何 activity 存在。如果杀死存在 Activity 的进程,有可能关闭用户正在使用的程序,或者使应用程序恢复的时延变大,从而影响用户体验;必须无 broadcast receiver。运行 broadcast receiver 一般都在等待一个事件的发生,用户并不希望此类程序被系统强制关闭;进程中 service 的数量必须为 0。存在 service 的进程很有可能在为一个或者多个程序提供某种服务,如 GPS 定位服务。杀死此类进程将使其他进程无法正常服务。
步骤三,再次检查当前运行的进程,如果 mRemovedProcesses.size() 仍然大于 curMaxProcs,则放宽条件再次进行回收。
步骤四,上面 3 个过程都是针对整个 process 进行的资源回收。在以上过程执行完毕之后,将在更小的粒度上对 Activity 的资源进行回收。与上面所述类似,列表 mLRUActivities 存储了当前所有运行中的 Activity,排序规则同样为最少访问原则。mLRUActivities.size() 返回系统中运行的 Activity 的数量,当其大于 MAX_ACTIVITIES(MAX_ACTIVITIES 是一个常量,一般值为 20,代表系统中最大允许同时存在的 Activity)时。将回收部分满足条件的 Activity 以减少内存的使用。 这里回收的只是 Activity 的内存资源,并不会杀死进程,也不会影响进程的运行。当进程需要调用被杀掉的 Activity 时,可以从保存的状态中回复,当然可能需要相对长一点的时延。
Linux 内核中的内存回收
lowmemorykiller
trimApplications() 函数中会执行一个叫做 updateOomAdjLocked() 的函数,如果返回 false,则执行默认回收,若返回 true 则不执行默认内存回收。
updateOomAdjLocked 将针对每一个进程更新一个名为 adj 的变量,并将其告知 Linux 内核,内核维护一个包含 adj 的数据结构(即进程表),并通过 lowmemorykiller 检查系统内存的使用情况,在内存不足的情况下杀死一些进程并释放内存。
由于 Android 操作系统中的所有应用程序都运行在独立的 Dalvik 虚拟机环境中,Linux 内核无法获知每个进程的运行状态,也就无法为每个进程维护一个合适的 adj 值,因此,Android Application Framework 中必须提供一套机制以动态的更新每个进程的 adj。这就是 updateOomAdjLocked()。
Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级:
IMPORTANCE_FOREGROUND:
IMPORTANCE_VISIBLE:
IMPORTANCE_SERVICE:
IMPORTANCE_BACKGROUND:
IMPORTANCE_EMPTY:


猜你喜欢
- webflux介绍Spring Boot 2.0spring.io 官网有句醒目的话是:BUILD ANYTHING WITH SPRING
- 前言最近接手的项目里涉及到了 GIF 动图的播放与监听,在上一版本中对于 GIF 的处理是由 H5 来实现的,因为考虑到用户体验,因此现在的
- 这篇文章主要介绍了Java 使用Calendar类输出指定年份和月份的日历,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参
- 概述最近重新回顾了一下数据结构和算法的一些基本知识,对几种排序算法有了更多的理解,也趁此机会通过博客做一个总结。1.选择排序-简单选择排序选
- Spring Aop的原理Spring的AOP就是通过 * 实现的。当为某个Bean或者某些Bean配置切面时,Spring会为其创建代理
- 前言Groovy 是一种基于 JVM 的动态语言,与 Java 语言紧密集成,可以很方便地在 Java 项目中使用。Groovy 有着简洁的
- 一、基于 Windows 的标准计时器(System.Windows.Forms.Timer)首先注意一点就是:Windows 计时器是为单
- Java 虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途,如图所示:程序计数器程序计数
- 目录知识点介绍正文1、质量压缩2、采样率压缩3、缩放法压缩4、RGB_565 通过改变图片格式来实现压缩总结知识点介绍Android 中图片
- 前言Future的问题写多线程程序的时候,可以使用Future从一个异步线程中拿到结果,但是如果使用过程中会发现一些问题:如果想要对Futu
- 前提最近因公司需要写了一个sdk,本想用本地库去做,但是遇到了各种问题,所以尝试着做成网络库去接入。之前一直没接触过这块,相对来说挺简单,但
- 项目里使用了Feign进行远程调用,有时为了问题排查,需要开启请求和响应日志下面简介一下如何开启Feign日志:注:本文基于spring-b
- 1. 可空类型修饰符(?):引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空。例如:
- 本文实例讲述了C#手工双缓冲技术。分享给大家供大家参考。具体如下:using System;using System.Collections
- 本文实例为大家分享了Android创建可拖动图片控件的具体代码,供大家参考,具体内容如下重载、自绘1、从View派生一个控件类 ,构造函数中
- 本文为大家分享了Android网络连接判断与相关处理,供大家参考,具体内容如下获取网络信息需要在AndroidManifest.xml文件中
- mybatis官网中文文档:https://mybatis.org/mybatis-3/zh/sqlmap-xml.htmlmybatis-
- Android中的ListView应该算是布局中几种最常用的组件之一了,使用也十分方便,下面将介绍ListView几种比较常见的优化方法:首
- 目录一 前言二 压缩文件2.1 压缩多个文件2.2 压缩文件或文件树2.3 借助文件访问器压缩三 解压文件四 总结一 前言项目开发中,总会遇
- 有时候我们做Android开发,需要弹一个用户提示,但是有时候设计的提示弹窗是带有图片的,我们每次写一个特别麻烦。所以我特地封装了一个工具类