OOM问题原理解析(一):adj内存管理机制

*本文主要介绍Android的lowmemorykiller的oom_adj的相关概念,以及根据一些案例来阐述了解oom_adj对于做Android应用开发的重要意义。*

*一、lowmeorykiller中进程的分类以及各类进程的adj值*

*在Android的lowmemroykiller机制中,会对于所有进程进行分类,对于每一类别的进程会有其oom_adj值的取值范围,oom_adj值越高则代表进程越不重要,在系统执行低杀操作时,会从oom_adj值越高的开始杀。********系统lowmemeorykiller机制下对于进程的级别的以变量的形式定义在framework/base/core/java/com/android/server/am/ProcessList.java类中,可总结成下表:*

img

*再补充介绍一下:*

*1.AMS角度对于进程的分级*

*上表带分级只是从lowmemroykiller角度来分的,时用于lowmemeorykiller执行杀进程操作,但是从android的系统管理角度看,即是从AMS执行相关逻辑时,又有一套自己的分级机制,当然这两套机制也有着很多互通的点。AMS角度的级别划分以变量的形式定义在framework/base/core/java/android/app/ActivityManager.java类中,以PROCESS_STATE开头的变量。*

*2.没有stopService其内含activity的后台进程*

*这类进程从lowmemorykiller角度是划分为cached,因为如果这类进程往往占有较大的内存,这类含有activity的后台进程往往占有较大内存,所以即使这类进程包含了Service,lowmemorykiller的机制也会更加倾向于优先杀死这类进程。*

*但是一般启动了服务的进程往往是希望服务在后台能够执行某些任务,这样看是不希望这些服务因为进程被杀而过早的被终止的,那如何调和这种矛盾呢?正确的做法是,对于期望较长时间留在后台的服务,应该将服务运行在单独的进程里,即是UI进程与Servie进程分离,这样期望长时间留在后台的Serivce会存在与一个被lmk分类为Service 进程的服务而获得较小的Adj值,而占有大量内存的UI进程则会分类为Cached进程,能够在需要的时候更快地被回收。*

*还有一点,这类进程虽然被lmk划分为cached进程,但是从ams角度是被划分为PROCESS_STATE_SERVICE这个类别的,即视为服务进程,在ams相关流程中也是以服务进程来执行相关逻辑的,此外在使用dumpsys meminfo查看所有进程时,这类进程也是被列在B service这个类别的。*

*3.A-Service与B-Service的划分*

*所有启动了服务的进程,且该服务所在的进程没有显示过UI,且该服务未执行startForeground(执行后会变为perveptible服务)动作,那该进程则为A-Service与B-Service中的一种。然后根据这类服务进程所处于Lru进程表中的位置,前1/3点服务为A-Service,其余的则为B-Service。*

*4.perceptible的标准*

*perceptible名为可感知的进程,但并不是说能够感知到进程就一定表示该进程属于perveptible进程,比如播放音乐的进程活着状态栏上有通知的进程,虽然能够感知到进程的存在,但是不代表进程一定时perceptible类别的进程。决定该进程是否属于perceptible进程并未进程的可感知性,而是该进程的服务是否执行了startForeground动作。*

*二、如何查询应用的adj级别*

*1.dumpsys meminfo*

*使用dumpsys meminfo命令时,会列出当前系统的所有进程,不同进程放入不同的分类,对应的分类名基本与lmk的分类一致。有一点不同的就是,退到后台启动了服务且显示过UI的进程,在dumpsys meminfo命令中会归为b service一类,但从lmk角度分配的oom_adj值为9~16的范围,属于cached一类*

*2.cat /proc/[PID]/oom_adj: 使用该命令会直接显示出对应进程号的adj值*

*三、未控制好oom_adj的案例*

*1.ui进程启动service的隐患*

*案例a:备份进程启动一个服务开始执行备份,备份服务运行在ui进程(服务未调用startForeground())*

*隐患:备份服务一般需要较长时间,在用户按Home键退出后台后,备份进程会处于previous状态,继续使用手机其他应用,会是使得备份进程处于cch-started-ui-services的状态,即是启动了服务并且包含ui的进程退到后台状态,此时进程的adj值处于9~16,随着时间推移逐渐增大。如果在较长的备份过程中,触发了lowmemorykiller,很容易导致备份进程被杀掉,从而导致备份的失败。*

*案例b:****备份进程启动一个服务开始执行备份,备份服务运行在ui进程(服务调用了startForeground())*****

****隐患:这种情况下备份进程会被划分为perceptible进程,基本上是不会被lowmemorykiller杀掉的,但是这也导致内存占用较大的备份常驻了,从内存管理角度来将,备份进程的UI部分是并不期望他常驻的,而大量内存的常驻也容易导致lowmemorykiller的出现,从而导致系统进入内存较低的等级,而当系统处于内存较低等级时,会触发系统回调所有进程进行进程回收动作,容易导致系统卡顿场景的出现。此外,********调用了startForeground()会导致进程被系统判定为前景进程,这样备份进程便会抢占用户操作手机时前台应用的cpu资源,增加了卡顿场景出现的几率。**************

*解决方法:将Service运行在独立的进程,这样应用退到后台后,备份服务进程会处于A-Service中(逐渐掉落到B-Service),而B-Service进程一般也是很难被lowmemorykiller砍。该独立********是否要startForeground()?如果期望保证备份尽快到完成,便可以牺牲一些用户在操作其他应用时到用户体验,将服务推为前景应用;对于很多需要保证功能的流畅运行的服务进程,例如音乐播放,录音等,则需要将这类服务进程通过********startForeground()设置为前景进程,但前提还是需要做到ui与Service分离。*********

*2.使用线程解决耗时操作造成anr问题的隐患*

*案例:短信、邮件、或笔记本应用,在用户按BACK键时存下草稿*

?

public` `class` `MyActivity extents Activity {``  ` `public` `void` `onPause(){``    ` `//存储草稿``  ` `}``}

*问题(1):由于存储草稿定操作一般时保存到数据库,某些情况下可能会占用较长时间,这里就有可能导致anr的隐患*

*解决方案1:*

?

public` `class` `MyActivity extents Activity {``  ` `public` `void` `onPause(){``    ` `new` `Thread() {``      ` `public` `void` `run() {``        ` `//存储草稿``      ` `}``    ` `}.start()``  ` `}``}

*问题(2):当用户以back键离开应用时(以home键离开会处于previous状态),应用退到后台处于empty-cached状态,内存不足时,可能会立刻杀。*

*解决方案2:如果线程有这问题,是否可以用服务来完成存储草稿的动作呢?*

*问题(3):如果用服务来存储草稿,即将存储草稿动作写在onStartCommand中,由于onStartCommmand操作依旧是执行在主线程的,所以在其中执行耗时操作时,依旧可能会导致ANR*

*最终解决方案:使用IntentService来执行保存草稿的动作*

?

public` `class` `MyActivity extents Activity {``  ` `public` `void` `onPause(){``    ` `... ``    ` `startService(` `new` `Intent(` `this` `, MyIntentService.` `class` `));``    ` `...``  ` `}``}` `public` `class` `MyIntentService ``extends` `IntentService {``  ` `protected` `void` `onHandleIntent(Intent intent) {``    ` `//保存草稿``  ` `}``}

*3.provider被binder导致的隐患*

*案例:systemui进程获取手机中的手机管家应用提供的content provider,用于获取当前应用相关信息*

*问题:管家应用的UID为System,在Android机制中,System UID进程提供的provider一旦被访问,即使访问完成关掉provider后,连接依旧存在,所有管家应用由于其provider持续被persistent进程咬住,所以管家应用便会长时间处于foreground级别的应用中,oom_adj为0,导致管家应用占用的大量内存很难被回收*

*解决方案:单独使用一个进程提供provider,提供provider进程由于占用内存较小,所以即使无法被回收也影响较小,这样管家应用的UI进程能够按照系统正常的回收流程在需要时被回收*

*四、总结一些经验*

*1.进程在启动服务后,在事情做完后,必须呼叫stopService或stopSelf通知框架,避免事情做完了,服务进程依旧常驻内存*

*2.对于需要长时间停留在后台的服务,且服务设置为具有重启特性时,需要做到ui与service分离,一避免占用内存较大的ui进程的常驻*

*3.对于需要长时间停留在后台的服务,且服务设置为具有重启特性时,不可长时间bind住其他进程的service或provider,避免其他进程常驻;*

*4.常驻性质的进程(oom_adj<=6),不可一直bind住其他进程的服务或provider,用完必须马上放掉*

*5.不可使用SystemUID进程内的provider,在Android设计中,若针对System UID的进程使用provider,即使已关掉provider,但框架仍会保持provider connection*

*6.利用onStartCommand方法回传值(START_STICKY,START_NOT_STICKY等)控制好服务的重启属性,在设计时充分考虑进程被lmk杀死的情况*

*7.IntentService继承自Service,针对CPU scheduling、工作排程等都有完整实现,建议多采用IntentnService进行功能实现*

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 系列文章Java 内存模型Android 系统内存管理机制Android 性能优化(三)之内存管理Android ...
    simplehych阅读 1,671评论 0 0
  • 如需转载请评论或简信,并注明出处,未经允许不得转载 目录 前言 Android 是基于 Linux 内核实现的操作...
    Geekholt阅读 3,252评论 1 11
  • Android内存管理机制 [转载自大果仁Pareto的 android内存管理机制] 1、基于Linux内存管理...
    月落3804阅读 836评论 0 3
  • 部分内容出至林学森的Android内核设计思想。Android官网内存管理部分出至简书https://www.ji...
    ArcherZang阅读 2,118评论 0 2
  • 本文主要包括三大部分内容:内存管理基础:从整个计算机领域简述主要的内存管理技术。Linux的内存管理机制:Andr...
    未子涵阅读 7,392评论 16 23