解读Android进程优先级ADJ算法

news2024/11/16 3:50:58

本文基于原生Android 9.0源码来解读进程优先级原理,基于篇幅考虑会精炼部分代码

一、概述

1.1 进程

Android框架对进程创建与管理进行了封装,对于APP开发者只需知道Android四大组件的使用。当Activity, Service, ContentProvider, BroadcastReceiver任一组件启动时,当其所承载的进程存在则直接使用,不存在则由框架代码自动调用startProcessLocked创建进程。一个APP可以拥有多个进程,多个APP也可以运行在同一个进程,通过配置Android:process属性来决定。所以说对APP来说进程几乎是透明的,但了解进程对于深刻理解Android系统是至关关键的。

1.2 优先级

Android系统的设计理念正是希望应用进程能尽量长时间地存活,以提升用户体验。应用首次打开比较慢,这个过程有进程创建以及Application等信息的初始化,所以应用在启动之后,即便退到后台并非立刻杀死,而是存活一段时间,这样下次再使用则会非常快。对于APP同样希望自身尽可能存活更长的时间,甚至探索各种保活黑科技。物极必反,系统处于低内存的状态下,手机性能会有所下降;系统继续放任所有进程一直存活,系统内存很快就会枯竭而亡,那么需要合理地进程回收机制。

到底该回收哪个进程呢?系统根据进程的组件状态来决定每个进程的优先级值ADJ,系统根据一定策略先杀优先级最低的进程,然后逐步杀优先级更低的进程,依此类推,以回收预期的可用系统资源,从而保证系统正常运转。

谈到优先级,可能有些人会想到Linux进程本身有nice值,这个能决定CPU资源调度的优先级;而本文介绍Android系统中的ADJ,主要决定在什么场景下什么类型的进程可能会被杀,影响的是进程存活时间。ADJ与nice值两者定位不同,不过也有一定的联系,优先级很高的进程,往往也是用户不希望被杀的进程,是具有有一定正相关性。

1.3 ADJ级别

ADJ级别 取值 含义
NATIVE_ADJ -1000 native进程
SYSTEM_ADJ -900 仅指system_server进程
PERSISTENT_PROC_ADJ -800 系统persistent进程
PERSISTENT_SERVICE_ADJ -700 关联着系统或persistent进程
FOREGROUND_APP_ADJ 0 前台进程
VISIBLE_APP_ADJ 100 可见进程
PERCEPTIBLE_APP_ADJ 200 可感知进程,比如后台音乐播放
BACKUP_APP_ADJ 300 备份进程
HEAVY_WEIGHT_APP_ADJ 400 重量级进程
SERVICE_ADJ 500 服务进程
HOME_APP_ADJ 600 Home进程
PREVIOUS_APP_ADJ 700 上一个进程
SERVICE_B_ADJ 800 B List中的Service
CACHED_APP_MIN_ADJ 900 不可见进程的adj最小值
CACHED_APP_MAX_ADJ 906 不可见进程的adj最大值

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

省去lmk对oom_score_adj的计算过程,Android 7.0之前的版本,oom_score_adj= oom_adj * 1000/17; 而Android 7.0开始,oom_score_adj= oom_adj,不用再经过一次转换。

1.4 LMK

为了防止剩余内存过低,Android在内核空间有lowmemorykiller(简称LMK),LMK是通过注册shrinker来触发低内存回收的,这个机制并不太优雅,可能会拖慢Shrinkers内存扫描速度,已从内核4.12中移除,后续会采用用户空间的LMKD + memory cgroups机制,这里先不展开LMK讲解。

进程刚启动时ADJ等于INVALID_ADJ,当执行完attachApplication(),该该进程的curAdj和setAdj不相等,则会触发执行setOomAdj()将该进程的节点/proc/pid/oom_score_adj写入oomadj值。下图参数为Android原生阈值,当系统剩余空闲内存低于某阈值(比如147MB),则从ADJ大于或等于相应阈值(比如900)的进程中,选择ADJ值最大的进程,如果存在多个ADJ相同的进程,则选择内存最大的进程。 如下是64位机器,LMK默认阈值图:

lmk_adj

在updateOomLevels()过程,会根据手机屏幕尺寸或内存大小来调整scale,默认大多数手机内存都大于700MB,则scale等于1。对于64位手机,阈值会更大些,具体如下。

private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
   
    ...
    for (int i=0; i<mOomAdj.length; i++) {
   
        int low = mOomMinFreeLow[i];
        int high = mOomMinFreeHigh[i];
        if (is64bit) {
   
            if (i == 4) high = (high*3)/2;
            else if (i == 5) high = (high*7)/4;
        }
        mOomMinFree[i] = (int)(low + ((high-low)*scale));
    }
}

二、解读ADJ

接下来,解读每个ADJ值都对应着怎样条件的进程,包括正在运行的组件以及这些组件的状态几何。这里重点介绍上图标红的ADJ级别所对应的进程。

Android系统中计算各进程ADJ算法的核心方法:

  • updateOomAdjLocked:更新adj,当目标进程为空或者被杀则返回false;否则返回true;
  • computeOomAdjLocked:计算adj,返回计算后RawAdj值;
  • applyOomAdjLocked:应用adj,当需要杀掉目标进程则返回false;否则返回true。

当Android四大组件状态改变时会updateOomAdjLocked()来同步更新相应进程的ADJ优先级。这里需要说明一下,当同一个进程有多个决定其优先级的组件状态时,取优先级最高的ADJ作为最终的ADJ。另外,进程会通过设置maxAdj来限定ADJ的上限。

关于分析进程ADJ相关信息,常用命令如下:

  • dumpsys meminfo,
  • dumpsys activity o
  • dumpsys activity p

2.0 ADJ<0的进程

  • NATIVE_ADJ(-1000):是由init进程fork出来的Native进程,并不受system管控;
  • SYSTEM_ADJ(-900):是指system_server进程;
  • PERSISTENT_PROC_ADJ(-800): 是指在AndroidManifest.xml中申明android:persistent=”true”的系统(即带有FLAG_SYSTEM标记)进程,persistent进程一般情况并不会被杀,即便被杀或者发生Crash系统会立即重新拉起该进程。
  • PERSISTENT_SERVICE_ADJ(-700):是由startIsolatedProcess()方式启动的进程,或者是由system_server或者persistent进程所绑定(并且带有BIND_ABOVE_CLIENT或者BIND_IMPORTANT)的服务进程

再来说一下其他优先级:

  • BACKUP_APP_ADJ(300):执行bindBackupAgent()过程的进程
  • HEAVY_WEIGHT_APP_ADJ(400): realStartActivityLocked()过程,当应用的privateFlags标识
  • PRIVATE_FLAG_CANT_SAVE_STATE的进程;
  • HOME_APP_ADJ(600):当类型为ACTIVITY_TYPE_HOME的应用,比如桌面APP
  • PREVIOUS_APP_ADJ(700):用户上一个使用的APP进程

SYSTEM_ADJ(-900)

SYSTEM_ADJ: 仅指system_server进程。在执行SystemServer的startBootstrapServices()过程会调用AMS.setSystemProcess(),将system_server进程的maxAdj设置成SYSTEM_ADJ,源码如下:

public void setSystemProcess() {
   
    ...
    ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
            "android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);
    mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());
    synchronized (this) {
   
        ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0);
        app.persistent = true;
        app.pid = MY_PID;
        app.maxAdj = ProcessList.SYSTEM_ADJ;
        app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
        synchronized (mPidsSelfLocked) {
   
            mPidsSelfLocked.put(app.pid, app);
        }
        updateLruProcessLocked(app, false, null);
        updateOomAdjLocked();
    }
    ...
}

但system_server的ADJ并非等于-900,而是-800?是由于startPersistentApps()过程直接把其adj重新被设置为-800,这算是一个小BUG,但 其实目前来说对于ADJ<0的进程,LMK不会杀,两者没有什么区别。

PERSISTENT_PROC_ADJ(-800)

PERSISTENT_PROC_ADJ:在AndroidManifest.xml中申明android:persistent=”true”的系统(即带有FLAG_SYSTEM标记)进程,称之为persistent进程。对于persistent进程常规情况都不会被杀,一旦被杀或者发生Crash,进程会立即重启。

AMS.addAppLocked()或AMS.newProcessRecordLocked()过程会赋值:

场景1: newProcessRecordLocked

final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, boolean isolated, int isolatedUid) {
   
  String proc = customProcess != null ? customProcess : info.processName;
  final int userId = UserHandle.getUserId(info.uid);
  int uid = info.uid;
  ...
  final ProcessRecord r = new ProcessRecord(stats, info, proc, uid);
  if (!mBooted && !mBooting
          && userId == UserHandle.USER_SYSTEM
          && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
   
      r.persistent = true;
      r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
  }
  if (isolated && isolatedUid != 0) {
   
      r.maxAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
  }
  return r;
}

在每一次进程启动的时候都会判断该进程是否persistent进程,如果是则会设置maxAdj=PERSISTENT_PROC_ADJ。 system_server进程应该也是persistent进程?

场景2:addAppLocked

final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, String abiOverride) {
   
    ProcessRecord app;
    if (!isolated) {
   
        app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
                info.uid, true)<

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1410189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python小项目:口令保管箱

代码&#xff1a; #! python3 # python 编程-----口令保管箱passwords{emails: F7minlBDDuvMJuxESSKHFhTxFtjVB6,blog:VmALvQyKAxiVH5G8v01if1MLZF3sdt,luggage:12345,} import sys,pyperclip if len(sys.argv)<2:print(usage:python python3文件[accout]-copy accout pass…

大模型学习笔记一:大模型应用开发基础

文章目录 一、大模型一些概念介绍 一、大模型一些概念介绍 1&#xff09;产品和大模型的区别&#xff08;产品通过调用大模型来具备的能力&#xff09; 2&#xff09;AGI定义 概念&#xff1a;一切问题可以用AI解决 3&#xff09;大模型通俗原理 根据上文&#xff0c;猜测下…

100.乐理基础-五线谱-是否需要学习五线谱

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;99.乐理基础-简谱的多声部-CSDN博客 简谱与五线谱的区别&#xff0c;各自的优劣势、使用场景、范围等&#xff1a; 要搞懂这个问题&#xff0c;其实核心就是四个词&#xff1a;首调、固定调、单声部、多声部 首调、…

github ssh ssh-keygen

生成和使用 SSH 密钥对是一种安全的身份验证方式&#xff0c;用于在你的本地系统和 GitHub 之间进行身份验证。以下是在 GitHub 上生成和使用 SSH 密钥对的基本步骤&#xff1a; 1. 生成 SSH 密钥对 在命令行中执行以下命令来生成 SSH 密钥对&#xff1a; ssh-keygen -C &q…

计算机速成课Crash Course - 23. 屏幕 2D 图形显示

今天继续计算机速成课Crash Course的系列讲解。 更多技术文章&#xff0c;全网首发公众号 “摸鱼IT” 锁定 -上午11点 - &#xff0c;感谢大家关注、转发、点赞&#xff01; 计算机速成课Crash Course - 23. 屏幕& 2D 图形显示 (qq.com) 23. 屏幕& 2D 图形显示 这台…

YOLOv5改进系列(28)——添加DSConv注意力卷积(ICCV 2023|用于管状结构分割的动态蛇形卷积)

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

06.Elasticsearch应用(六)

Elasticsearch应用&#xff08;六&#xff09; 1.什么是分词器 ES文档的数据拆分成一个个有完整含义的关键词&#xff0c;并将关键词与文档对应&#xff0c;这样就可以通过关键词查询文档。要想正确的分词&#xff0c;需要选择合适的分词器 2.ES中的默认分词器 fingerprint…

软件游戏提示msvcp140.dll丢失的解决方法,全面分析msvcp140.dll文件

msvcp140.dll是Microsoft Visual C 2015 Redistributable的一部分&#xff0c;它包含了许多用于运行程序的函数和类库。当这个文件丢失或损坏时&#xff0c;依赖于该组件的应用程序可能无法正常启动&#xff0c;系统会弹出错误提示&#xff0c;告知用户找不到msvcp140.dll文件。…

【寒假打卡】Day02

文章目录 选择编程NC31 第一个只出现一次的字符面试题 01.01. 判定字符是否唯一 选择 以下程序运行后的输出结果是&#xff08; &#xff09; #include <stdio.h> void fun(char **p) { int i; for(i 0; i < 4; i) printf("%s", p[i]); } in t main() { …

河南嘉家购商贸有限公司获绿色积分信用认证

“实现绿色产业、打造完善的绿色产业链、走可持续发展共创共赢”。近日&#xff0c;河南嘉家购商贸有限公司获得绿色积分认证&#xff0c;确认了该企业在绿色消费积分领域的领先地位。 据了解&#xff0c;河南嘉家购商贸有限公司始终将绿色积分视为企业发展的核心要素。全面优化…

使用collection.Counter实现统计简化

背景介绍 在python的应用场景中&#xff0c;我们常常需要通过建立统计表来对某一个数据集中某些数据出现次数的统计&#xff0c;这时候经常会使用到python的字典数据类型建立映射表&#xff0c;一般的方法可能会比较繁琐&#xff0c;本篇博客介绍collection.Counter方法帮助简…

[Python] KDE图[作密度图(Kernel Density Estimate,核密度估计)]介绍和使用场景(案例)

KDE图是什么&#xff1f; 核密度估计&#xff08;Kernel Density Estimate&#xff0c;KDE&#xff09;是一种非参数统计方法&#xff0c;用于估计未知随机变量的概率分布。它通过在每个数据点附近放置一个核函数&#xff0c;并将这些核函数加总起来&#xff0c;得到对概率分布…

用可视化案例讲Rust编程4. 用泛型和特性实现自适配shapefile的读取

本节已经涉及Rust学习曲线上的一个大坑&#xff1a;泛型和特性了&#xff0c;属于语言的深水区&#xff0c;如果初学者&#xff0c;建议看一眼知道有这个功能即可。 如果我们立足于功能实现&#xff0c;那么做到像上一节那样就可以了&#xff0c;从原理上来说&#xff0c;每个…

javascript跳转微信: 扫码,分享链接,下载二维码

文章目录 微信后台配置扫码前端配置扫描二维码分享链接下载二维码 总结 微信后台配置扫码 参考&#xff1a; 微信二维码内容获取 在开发设置中配置扫码路由以及跳转路径 完成该配置后&#xff0c;可以通过扫描对应地址的二维码进入小程序的指定页面 前端配置 扫描二维码 …

【github】使用github action 拉取国外docker镜像

使用github action 拉取国外docker镜像 k8s部署经常用到国外镜像&#xff0c;如果本地无法拉取可以考虑使用github action环境 github action的ci服务器在国外&#xff0c;不受中国防火墙影响github action 自带docker命令运行时直接将你仓库代码拉取下来 步骤 你的国内dock…

测试人年终总结:入行三年,下一步怎么走,思想碰撞

原贴地址&#xff1a;入行三年&#xff0c;下一步怎么走&#xff0c;思想碰撞 TesterHome 熟悉环境&#xff0c;进步缓慢&#xff1b;停止思考&#xff0c;举步不前&#xff08;为什么会有这篇文章why 初心变质&#xff1a;计算机系毕业&#xff0c;毕业时的打算是从测试进&a…

如何实现人口、GDP等的空间化效果?

现在假设你遇到了某个情况&#xff0c;手头有某市十几个区县的人口总数但是你想要更加精细化的空间化数据产品&#xff0c;以方便后续进行核密低分析或者进行其它的空间、数据分析。 那我们应该如何通过十几个点的人口数据进行空间化呢&#xff0c;详细步骤如下&#xff1a; 第…

PHP“引用”漏洞

今日例题&#xff1a; <?php highlight_file(__FILE__); error_reporting(0); include("flag.php"); class just4fun { var $enter; var $secret; } if (isset($_GET[pass])) { $pass $_GET[pass]; $passstr_replace(*,\*,$pass); } $o unser…

Flume介绍

一、介绍 Apache Flume 是一种分布式、可靠且可用的系统&#xff0c;用于有效地收集、汇总大量日志数据&#xff0c;并将其从多个不同来源转移到集中式数据存储区。 Apache Flume 的使用不仅限于日志数据聚合。由于数据源是可定制的&#xff0c;Flume 可用于传输大量事件数据&a…

OpenCV书签 #互信息的原理与相似图片搜索实验

1. 介绍 互信息&#xff08;Mutual Information&#xff09; 是信息论中的一个概念&#xff0c;用于衡量两个随机变量之间的关联程度。在图像处理和计算机视觉中&#xff0c;互信息常被用来度量两幅图像之间的相似性。 互信息可以看成是一个随机变量中包含的关于另一个随机变…