解读 Android 17 全新内存限制,有没有“豁免”后门?

App 需要适配和做的基本在 Android 17 内存管理将严格管控,App 要注意适配都已经聊过了,这次 Android 17 正式版发布也就说明这个能力正式落地了。

简单说,Android 17 这套和之前的不带一样,不再是针对 Dalvik/ART heap size 场景做一些大小调整,核心是在系统层给 App 进程挂 cgroup v2 内存约束

简单说,它主要限制的是"进程总内存行为 ",尤其是匿名内存 + swap ,除了 Java heap,类似 native、Bitmap、WebView、GPU/图形相关缓存(大概率) 等都可能受到影响。

也就是之前抖音"极客"适配 Android 5 ~ 9 等老机型技术在这种情况下大概率也没办法很好继续发挥,因为改 ART 堆结构、扩 Region Space这些操作,解决不了系统从 cgroup 层面对整个进程的外部上限

另外,这次新增加的 Memory Limiter 是一个系统服务 ,用的是 Linux cgroup v2监控和限制应用进程内存,所以核心不再是 Runtime.maxMemory() 这中 Java heap 限制,实际用的是 cgroup v2 的两个内核接口:

  • memory.high:软上限,超过后内核会对该 cgroup 下的进程施加压力,尝试回收 file-backed memory,并把匿名内存换出到 swap
  • memory.swap.max:swap 使用上限,防止一个进程无限把匿名页挤进 ZRAM/swap

说人话就是,memory.high 警戒线,memory.swap.max 是最多能塞进去的上限,两个一起用就是防止一个 App 把真实内存和压缩内存都占满

另外 Memory Limiter 会和 Activity Manager Service / AMS 集成 ,用来跟踪进程生命周期与状态变化,然后通过 Linux kernel cgroup v2 文件系统执行限制。

所以它的执行链大概是:

  • ActivityManagerService 跟踪进程生命周期和状态
  • Memory Limiter 根据进程状态分配 visible / not visible / cached 对应限制,写入对应 cgroup v2 的 memory.highmemory.swap.max
  • 进程超过 memory.high 后,内核先回收、换出、限速
  • 如果继续分配匿名内存、swap 用尽,就会触发系统记录与终止

这个情况触发后,反馈在 App 侧看到的是一次没有 crash 堆栈的进程退出,而且这个场景下,最容易出现问题的就是存在内存泄漏的 App,可能出现的是内存泄漏,但是因为 Memory Limiter 被回收,以前的路径完全捕获不到问题。

当然,"天无绝人之路",还是有一些情况可以豁免:

1、默认只监控 App 进程, Memory Limiter 默认监控 UID >= 10000 的应用进程,系统进程通常豁免,这个很好理解

2、部分进程状态是 Unrestricted 的也可以豁免,所以这个还是很"灵活"的, 目前的情况主要如下表:

Android 进程状态 Memory Limiter 眼里的待遇 人话
PERSISTENT / PERSISTENT_UI Unrestricted 系统核心常驻进程,不按普通 App 限制
TOP visible 用户正在看、正在操作的 App
BOUND_TOP visible 被当前前台 App 绑定的重要进程
IMPORTANT_FOREGROUND visible 虽然不是主 Activity,但和当前用户交互强相关
TOP_SLEEPING visible 屏幕刚熄灭/锁屏,但刚才那个前台 App 还算高优先级
FOREGROUND_SERVICE not visible 有前台服务通知,但用户不一定正在看它
cached cached 用户已经离开,进程只是留在后台等下次打开更快

需要注意的是 foreground service 具体需要看真实的 process-state 映射表,现在 foreground service 不等于 visible 上了,很多后台常驻、上传、定位、音频、IM、同步类 App 过去靠 FGS 提升优先级,现在内存上限有可能会被按 not visible 处理。

说人话这几个状态可以这么理解:

  • Unrestricted:消防、电梯、安保控制室, 这些是系统核心部门,不能被限制
  • Visible:正在面对面办理业务的窗口, 用户正在看,正在用,优先给空间
  • Not visible:后勤办公室还在处理你的单子, 比如你已经离开窗口,但文件还在上传、订单还在处理,重要但不能占空间
  • Cached:临时空位上放着你刚才用过的资料, 留着是为了下次方便,不是必须留,空间紧张就先清掉

3、调试是可以通过 adb 设置 ignore 机制:

css 复制代码
adb shell am memory-limiter ignore <uid>|none|all
adb shell am memory-limiter manual <pid> <limit>|max|none
adb shell am memory-limiter status

所以可以看到,这套机制主要还是针对没有"门路"的普通 App ,另外这套机制是 vendor 配置驱动, 配置文件在 vendor 分区:

arduino 复制代码
/vendor/etc/memory-limiter-config.xml

如果这个文件不存在、不可读或无效,Memory Limiter 会被禁用, 也就是 Memory Limiter 是否启用、阈值多少都取决于设备配置。

配置文件可以定义多个 limitSet,系统会根据设备可用总内存选择最匹配的那一组,所有内存值单位是 MiB:

xml 复制代码
<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <limitSet>
      <!-- Limits for a phone with at least 14G of ram: 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>14336</minimumRequiredMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
字段 含义
minimumRequiredMemTotal 这个 limitSet 适用于"至少多少 MiB 可用系统内存"的设备
memVisible visible 进程的 memory.high
memNotVisible not visible 进程的 memory.high
swapVisible visible 进程的 memory.swap.max
swapNotVisible not visible 进程的 memory.swap.max

所以不同进程,大小配置也可能不一样,配置文件可以定义多个 limit sets,服务会根据设备可用 RAM 选择 best match。

比如 AOSP Demo 里 14 GiB RAM 的手机:

  • visible 进程内存上限示例是 8192 MiB
  • not visible 是 4096 MiB
  • visible / not visible 的 swap 上限是 4096 MiB

你可以在设备上直接看当前生效值:

lua 复制代码
adb shell am memory-limiter status

AOSP 文档给的示例输出是:

ini 复制代码
Memory limiter
  enabled                  monitoring=true          ignored=none
  visibleMem=1948MB        visibleSwap=974MB
  notVisibleMem=974MB      notVisibleSwap=487MB
  started=36               watched=36              watch-failed=0
  events=0                 processes=36            process-hwm=36

这里 visibleMem / notVisibleMem 就是当前设备算出来同时生效的绝对上限。

另外 Android 17 还有一个信的叫 PMGD (Process Memory Guardian Daemon), 它和 Memory Limiter 不是一回事 。

PMGD 更像是一个 vendor/system 进程级守护机制,可以按 /vendor/etc/pmgd/config.json 配置目标进程、memory.high 、 profile、匿名内存硬上限。

可以把 PMGD理解成厂商给某些"关键系统进程"单独装的内存保险丝, 比如厂商对某个 App 的内存使用比较担心,但是又不能让他被 Memory Limiter 直接秒杀,所以让 PMGD 去盯着它,它超过某个内存线后,先给系统一点时间回收,如果回收不下来就记录日志,然后停掉。

技术上就是,PMGD 使用 inotify 监听 cgroup v2 的 memory.events,命中后检查匿名内存,必要时记录 statsd atom 并杀进程,它的配置目标可以是 system_server 这类指定进程。

所以简单来说:

  • Memory Limiter 是"按人群管理" :前台 App 一档、后台 App 一档、cached 一档
  • PMGD 是"点名管理" :我就盯 进程,发现它不正常就处理

可以粗暴点理解,PMGD 就是针对 Memory Limiter 豁免的那些进程的一个另外配置监管支持,相比 Memory Limiter 可以有一段豁免时间, 不会立刻扑街。

相关推荐
Hyyy3 小时前
理解LLM的基本工作原理:预训练、微调、推理的区别
前端
Gatlin3 小时前
前端逆向与反逆向:一场猫鼠游戏的底层逻辑与实战
前端
Pedantic3 小时前
本地通知(Local Notifications)学习笔记
前端
森蓝情丶4 小时前
我给 AI 搭了个法庭:一个前端仔的 LangGraph 实战全记录
前端·后端
爱勇宝4 小时前
干了近 8 年,一夜之间被裁:AI 时代,程序员最该害怕的不是 AI
前端·后端·程序员
贾艺驰4 小时前
实战Android Framework: 新增一个系统权限
android
Pedantic4 小时前
Combine 框架学习笔记
前端
runnerdancer4 小时前
Agent如何加载执行Skill的脚本
前端·agent
yingyima5 小时前
VS Code 正则替换技巧:从凌晨3点的服务器报警开始
前端