一、问题的本质:为什么"已占用"的内存可以算作"空闲"?
在回答"为什么previous层级占用的内存可以算在freeRam内"之前,需要先厘清一个关键概念:Android系统中的"freeRam"并非Linux内核意义上的"MemFree"。
Linux内核中,/proc/meminfo的MemFree字段代表物理上完全未被任何对象使用的内存页面。而Android的freeRam是一个"逻辑可用内存"概念------它的定义是当前可供分配、或在内存压力下可被快速回收的内存总量。这一概念在dumpsys meminfo的输出中有明确体现:Free RAM = cached pss + MemFree(即内核空闲页)+ 其他可回收内存。
因此,previous层级的内存虽然在物理上被占用,但在语义上被系统视为"可回收候选",从而计入freeRam。下面从源码层面逐层剖析这一机制。
1.1 进程优先级层级体系(ProcessList中的ADJ定义)
Android在ProcessList.java中定义了完整的进程优先级层级体系,每个层级对应一个adj值,adj值越大表示进程越不重要,越容易被LMK在内存压力下回收:
java
UNKNOWN_ADJ = 1001 最低优先级(缓存进程上限)
PREVIOUS_APP_ADJ = 700 用户上一个使用的应用
HOME_APP_ADJ = 600 Home桌面进程
SERVICE_ADJ = 500 包含Service的进程
HEAVY_WEIGHT_APP_ADJ = 400 后台重量级进程
BACKUP_APP_ADJ = 300 备份进程
PERCEPTIBLE_APP_ADJ = 200 可感知进程
VISIBLE_APP_ADJ = 100 可见进程
FOREGROUND_APP_ADJ = 0 前台进程
1.2 previous层级的特殊性:介于"活跃"与"可回收"之间
previous层级之所以特殊,在于它处于内存管理语义的边界状态:
-
它不是前台进程:用户已离开,不再有可见的UI交互,从用户体验角度,可以安全地回收其内存而不会造成直观的体验损失;
-
但它也不是普通缓存进程:用户可能随时通过"最近任务"或简单的返回操作切换回来,因此系统倾向于优先保留它(LRU列表中的头部位置)。
正是这种"可以回收,但优先保留"的双重属性,使得previous进程的内存在统计上被纳入freeRam,在管理策略上却享有高于其他缓存进程的保护。
二、内存统计模型:AMS如何计算freeRam
2.1 统计架构:MemInfoReader + dumpsys meminfo
Android系统的内存统计通过MemInfoReader类从/proc/meminfo中读取底层数据,再结合AMS管理的进程信息进行汇总计算。dumpsys meminfo命令的输出结构清晰地展示了这个统计模型:
java
Total RAM: 物理内存总量
Free RAM: 可用内存 = 缓存进程内存 + 内核空闲页 + 其他可回收内存
Used RAM: 已用内存 = 应用实际使用量(PSS) + 内核占用
其中,Free RAM的计算公式可以近似表达为:
java
Free_RAM = MemFree + Cached(Page Cache)+ Cached_PSS(缓存进程的PSS总和)
previous进程的内存被归入Cached_PSS部分,从而计入Free RAM。
2.2 computeOomAdjLocked:进程定级与previous识别的关键逻辑
在ActivityManagerService.java中,computeOomAdjLocked方法是决定进程所属层级和adj值的核心入口。相关源码逻辑可抽象如下:
java
// 位于 ActivityManagerService.java 的 computeOomAdjLocked 方法中
if (app == mPreviousProcess && app.activities.size() > 0) {
if (adj > ProcessList.PREVIOUS_APP_ADJ) {
// 该进程是用户上一个显示UI的进程,赋予PREVIOUS_APP_ADJ
adj = ProcessList.PREVIOUS_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
}
}
上述逻辑表明:
-
mPreviousProcess:AMS维护的一个引用,指向用户最近一次交互的进程。当用户切换到新应用时,旧应用进程被记录为mPreviousProcess; -
判据:
app == mPreviousProcess && app.activities.size() > 0------只有确实包含Activity的进程才被识别为previous; -
adj赋值:被识别为previous的进程,其adj会被设置为
PREVIOUS_APP_ADJ(700),归类为高优先级的缓存进程。
2.3 updateOomAdjLocked:将previous纳入缓存进程统计
在updateOomAdjLocked(或相关统计方法)中,系统遍历所有进程并按其adj值分类统计。previous进程在统计视角下被归入缓存进程类别,其PSS(Proportional Set Size,比例内存占用)被计入cached pss,进而被纳入Free RAM的计算中。
这意味着:在系统的统计口径中,previous进程的内存既是"已使用"的(物理上被进程占用),又是"可用"的(逻辑上可被LMK快速回收后重新分配)。
2.4 ProcessRecord.curAdj与进程分类的关联
每个Java进程在AMS中都有一个对应的ProcessRecord对象,其成员变量curAdj记录了该进程当前的优先级。previous进程的curAdj == PREVIOUS_APP_ADJ(700),在以下场合发挥作用:
-
统计分类:
curAdj决定该进程在dumpsys meminfo输出中被归入哪一类; -
LMK回收决策:
curAdj值越大,在内存压力下越优先被LMK选中回收; -
freeRam计算:分类为缓存进程后,其内存被计入Free RAM的cached部分。
三、可回收性:为什么previous内存可以算作"可用"
3.1 内核视角:Page Cache的可回收属性
从Linux内核的角度,内存页面大致分为三类:
|-----------------|---------------------|--------------------|
| 页面类型 | 说明 | 可回收性 |
| Free pages | 未被使用的物理RAM | 立即可分配 |
| Cached pages | 文件支持的页面(代码、mmap文件等) | 可快速回收(脏页需写回) |
| Anonymous pages | 堆、栈等非文件支持的页面 | 不可直接回收(需swap/zRAM) |
Android使用的核心原则是:"free memory is wasted memory"(空闲内存就是浪费)。系统尽量将所有RAM用于缓存最近使用的应用和数据,而不是让其空闲。
previous进程的内存以Cached pages为主(应用的代码映射、资源文件等),这些页面可以被内核的kswapd机制快速回收------如果是clean cached pages,可以直接丢弃;如果是dirty pages,需要先写回存储或压缩到zRAM。这种可快速回收的特性,是previous内存被纳入freeRam统计的内核层面基础。
3.2 LMK回收机制与previous的回收时机
Android的Low Memory Killer(LMK)机制根据adj值决定进程的回收顺序。在ProcessList中,不同adj层级被映射到对应的minFree阈值(内存下限),当系统可用内存低于某个闸值时,该层级及更高adj的进程将被回收。
previous进程的adj为700,属于缓存进程层级。当内存压力达到回收缓存进程的阈值时,previous进程虽然位于LRU列表头部(比其他缓存进程更晚被回收),但依然在可被回收的范畴内。
3.3 内存的"快速回收"属性 vs "物理空闲"属性
这是理解整个机制的最关键区别:
-
物理空闲:内存页面上没有任何数据,可以立即分配给新请求者。对应内核
MemFree。 -
逻辑可用:内存页面虽然被占用,但承载的数据可以通过丢弃或迁移被快速释放,从而变为物理空闲。对应Android的
Free RAM概念。
previous进程的内存属于后者------它在物理上是"used",但在语义上是"available"。当系统需要内存时,LMK可以杀掉该进程,内核回收其页面,内存即可重新分配。整个过程的耗时在毫秒级别,对用户体验的影响远小于触发OOM。
四、Android 16的演进变化(2025-2026)
4.1 内存管理策略的延续与增强
Android 16在内存管理方面的演进体现在优化而非重构。根据2025-2026年的开发资料,Android 16延续了"free memory is wasted memory"的核心理念,并进行了以下增强:
-
更高效的垃圾回收机制:优化了ART运行时的GC策略,减少了应用在后台时的内存压力;
-
动态内存分配策略:引入了更细粒度的内存分配控制,使系统能更精确地平衡各层级进程的内存占用;
-
后台任务调度增强:改进了后台进程的约束机制,减少不必要的后台内存消费。
4.2 previous层级在Android 16中的意义
在Android 16中,previous层级的重要性不仅没有减弱,反而因为多任务场景的日益丰富而更加关键:
-
快速切换体验:用户频繁在2-3个应用间切换的场景越来越普遍,保留previous进程在内存中能显著减少冷启动的延迟和功耗;
-
LRU列表头部保护:previous进程被置于缓存LRU列表的头部位置,这是连接"活跃进程"和"缓存进程"的关键过渡层级;
-
freeRam统计的合理性:将previous内存计入freeRam,使系统报告的内存状态更准确地反映了"在必要时可以释放的内存",而非"当前完全空闲的内存",这对应用层的内存压力判断(如
onTrimMemory回调)至关重要。
五、总结
5.1 核心原理总结
previous层级占用的内存可以算在freeRam内,本质原因在于Android的freeRam定义的不是物理空闲内存,而是逻辑可回收内存。这一设计建立在四层基础之上:
|-----|----------------------------------------------------------------|--------|
| 层面 | 关键机制 | 作用 |
| 语义层 | Free RAM ≠ MemFree,而是包含可回收的cached pages | 定义统计口径 |
| 框架层 | AMS通过computeOomAdjLocked识别previous进程,设置curAdj=PREVIOUS_APP_ADJ | 进程定级分类 |
| 统计层 | previous进程的内存被计入cached pss,纳入Free RAM计算 | 内存统计汇总 |
| 内核层 | Page cache的快速回收特性 + LMK的adj回收机制 | 保证可回收性 |
5.2 设计哲学
Android的这种设计体现了一个深刻的内存管理哲学:
"空闲内存是浪费,但随时可释放的缓存内存是财富。"
系统通过将previous层级的内存统计为freeRam,引导应用层和用户正确理解内存状态------这些内存虽然在用,但随时可以被释放以满足新的内存需求。这种"逻辑空闲"视角使Android能够在有限的内存资源下,同时实现高效的多任务体验和灵活的内存压力应对。
5.3 关键要点回顾
-
previous层级对应
PREVIOUS_APP_ADJ = 700,是用户上一个使用的应用,处于活跃进程与缓存进程的过渡边界; -
freeRam的构成包括内核的Free pages、Page Cache的Cached pages以及AMS管理的缓存进程内存(含previous进程);
-
可回收性是关键:previous进程的内存以文件映射页面为主,可被内核快速回收,这是其被纳入freeRam统计的内核基础;
-
AMS的核心方法
computeOomAdjLocked负责将用户上一个进程识别为previous并赋予相应的adj值,使其在统计口径上被归类为"可回收缓存"; -
LMK的回收策略保证了在内存压力下,previous进程虽然享有LRU头部保护,但在必要时仍可被回收,从而兑现freeRam统计中的"可用"承诺。