Jimmy Chen

A Programmer

(原创)基于Android 8.1之updateOomAdjLocked源码分析

  在lowmemorykiller的源码分析中,我们了解到在内存紧张的情况下,lmkd会根据oom的分数值,对应用进行清杀操作。上一篇主要讲lmkd驱动及lmkd守护进程的相关内容,还没有涉及到oom分数值更新的分析,所以这一篇我们分析updateOomAdjLocked方法是如何更新oom分数值的,另外分析外updateOomAdjLocked方法后,还能了解到除了lmkd因低内存杀死进程外的另一种内存清理操作。

updateOomAdjLocked分析

  updateOomAdjLocked在AMS中存在重载函数,分别是不带参数的、带一个参数的和带五个参数的,这里我们主要分析不带参数的重载函数。不带参数的重载函数内容比较多,所以这里只能分段分析

  如果当前前台有进程,那么TOP_ACT和TOP_APP就代表当前前台进程,否则就代表栈顶进程。ProcessList.MAX_EMPTY_TIME;代表空进程最长存活时间,定义在ProcessList.java中,一般为30分钟。N为Lru排序的链表中的存活进程的数量。

  emptyProcessLimit代表系统空进程数量的上限,cachedProcessLimit代表的是缓存进程的数量上限,这两个进程的上限值是可以设置。接下来的代码我们先脱离updateOomAdjLocked主线,先分析emptyProcessLimit是怎么设置的。另外,numSloas计算过程中除以2是因为每个槽配置到进程包含后台进程和empty进程两种,两种进程需用不同adj值表示,所以每个槽包含两个adj值的分配空间,所以需要除以二,计算出来的numSlots值为3。其中,emptyFactor表示每个adj包含的空进程个数,而cacheFactor表示每个adj包含的cache的空进程数量。

emptyProcessLimit上限设置分析

  之前的代码中emptyProcessLimit设置为mConstants.CUR_MAX_EMPTY_PROCESSES;,而mConstants是一个ActivityManagerConstants类对象。所以可以直接查看该类的代码

  上面的代码已经解析得很清楚了。如果内存比较紧张的话,其实可以将MAX_CACHED_PROCESSES设置低一些,这样系统中的空进程和缓存进程数量就会降低从而释放部分内存,不过这样也会带来副作用,就是跟默认情况相比,缓存下来的进程减少了,可能会导致后台存留的进程数量减少,那么用户在使用过程中命中缓存进程的几率就降低,影响用户体验。不过再进一步想想,系统内存都已经不住了,lmkd也不会让系统驻留这么多空进程和缓存进程的。具体大家可以自己试验。

回到updateOomAdjLocked继续分析

  上面这部分没有什么特殊的处理,从这里开始,我们就需要遍历所有的应用,并对应用进行处理了。上面的代码只是简单对B service进行处理,并将距离上一次活动到现在时间最短的那个应用到selectedAppRecord中,这里既然记录下来了,那么后面肯定是要用到的,这里留个记号。

  这里的代码用到之前计算出来的cachedFactor和emptyFactor。这里举个例子说明一下,假设当前缓存进程为最大的16个,按照上面的计算有3个slot,那么每个slot就有5个进程,所以cacheFactor就等于5了。但是又按照上面写的代码,可以看到在adj为900的情况下,会存放5个空进程和5个缓存进程的,之后就是901用于存5个放缓存进程,而902用于存放5个空进程。之后,存放这两种进程的adj值步进都为2。这段代码执行效果大概如此,接下来我们接着分析。

  这上面就存在杀死进程的操作了。我们知道lmkd是在系统剩余内存达到某个临界点的时候才开始清除进程的。而这里则是空进程或者缓存进程数量超过系统规定值就开始清除一部分进程了。所以这两种清除进程的情况并不相同。

  上面内容简单,只是对设置了isolated熟悉的进程进行判断清除,以及统计adj值大于HOME的进程的数量。

  上面首先是对B service进行一番处理,不过暂时也看不出有什么影响。然后就根据numCached缓存进程的数量和numEmpty空进程的数量来判断当前系统内存紧张程度。这两类进程的数量越少,表示当前系统内存越紧张,内存等级就越高,内存等级由低到高分别为:ADJ_MEM_FACTOR_NORMAL、ADJ_MEM_FACTOR_MODERATE、ADJ_MEM_FACTOR_LOW和ADJ_MEM_FACTOR_CRITICAL,共四个等级。

  至于为什么缓存进程和空进程的数量越少会代表系统内存越紧张呢,这里解释一下。在讲解lowmemorykiller的时候有讲述到,如果系统内存达到阈值的话,会首先清除adj值最大的进程,而缓存进程和空进程的adj值属于最容易被清除的那一类。其次从上面可以看出,ActivityManagerService是尽可能保留空进程和缓存进程的,只有在这两类进程的数量超过系统规定的数量才会清除,所以进程变少了只能是lmkd来清除的。既然lmkd都动手清除进程了,那么就很好理解,缓存进程和空进程数量越少的话,那么系统内存当然就越紧张了。

  这里判断如果内存因子级别不是NORMAL的话,就需要对所有的进行进行内存回收操作。switch中根据内存因子的级别设置fgTrimLevel为对应的级别。这里,也和之前对缓存进程和空进程的处理类似,进行分组,factor为每组进程的数量。

  mLruProcesses中的进程是按照Lru算法排列的,Lru是最近最少使用的意思。那么就是所mLruProcesses中第一个应用时最近最少使用到的。在系统内存紧张的情况下,当然要最近很少使用到的,被缓存起来的应用尽可能释放内存的。这里进行内存释放走的确实另一条道路了,这里会调用app.thread.scheduleTrimMemory要求应用进行内存释放。这个调用最后会调用到应用Activity的OnTrimMemory接口,这样开发者就可以在自己的应用中选择哪些内存是可以进行释放的,如果开发者没有实现这个接口进行内存释放,那么lmkd在内存较低的时候,很可能就会将你开发的应用杀死了。

  大于HOME级别的应用包括上一次使用的应用、缓存进程以及空进程。在释放之前,就已经将应用分组了的,如果应用数量较多,在不近step超过factor因子时,我们会将后续的Trim级别降低。

  冲上面往下看,应用的重要程度越来越高,重要程度不同的应用,所使用的Trim级别也不近相同。越不重要的应用,越应该尽可能多的释放内存,以确保系统内存主意维持其余重要进程或者新进程使用。上面这些都是当内存级别不是NORMAL情况下的处理,下面看看当内存紧张程度为NORMAL时,系统是如何处理的。

  如果系统内存充足的话,就只对PROCESS_STATE_IMPORTANT_BACKGROUND以上级别的应用以TRIM_MEMORY_UI_HIDDEN级别进行内存回收。到这里,基本上对应用的处理就完成了,这个方法后面还有一部分代码,但是看那部分代码是对UID进程处理的,貌似和现在分析的主题不是特别搭,而且暂时也没看懂做了什么特殊处理。所以剩余的代码,这里就不多做分析了。

总结

  updateOomAdjLocked方法完成下面三个操作

  1. 更新所有应用等oom_adj值,其中非cachedProcess和emptyProcess进程等oom_adj值的计算通过调用computeOomAdjLocked方法完成,而cachedProcess和emptyProcess进程则按照由近至远的方式交替从小至大的分配oom_adj值
  2. 根据cachedPrcess和emptyProcess的数量判断是否超过数量上限,超过则杀死进程,越久远的进程在超过时优先被杀死。
  3. 根据cachedPrcess和emptyProcess的数量判断当前内存的紧张程度生成对应等级,根据当前系统内存等级,来要求进程做对应的进程内的回收。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注