请准备好将 Android 应用的内存页大小过渡到 16 KB
准备好提升应用性能,因为 Android 正在采用 16 KB 内存页大小
Android 向 16 KB 页大小的过渡
传统上,Android 系统采用 4 KB 内存页大小运行。然而,许多 ARM 处理器(Android 手机中最常见的处理器)支持更大的 16 KB 页大小,可带来性能提升。自 Android 15 起,Android 操作系统实现了页大小无关性,允许设备在 4 KB 或 16 KB 页大小下高效运行。
自 2025 年 11 月 1 日 起,所有针对 Android 15+ 设备提交至 Google Play 的新应用及应用更新,若使用原生 C/C++ 代码,必须支持 16 KB 页面大小。这是确保您的应用在最新 Android 硬件上实现最佳性能的关键步骤。不包含原生 C/C++ 代码或依赖项、仅使用 Kotlin 和 Java 编程语言的应用已兼容,但如果您使用原生代码,现在是采取行动的时候了。
向更大的 16 KB 页面大小过渡将直接提升用户体验。配置为 16 KB 页面大小的设备可实现整体性能提升 5-10%。这意味着更快的应用启动时间(部分应用可提升 30%,平均提升 3.16%)、更优的电池使用效率(功耗降低 4.56%)、更快速的相机启动(提升 4.48-6.60%)以及更快速的系统启动(平均快约 0.8 秒)。尽管内存使用量有轻微增加,但更快的内存回收路径是值得的。

原生代码挑战——以及 Android Studio 如何助您应对
如果您的应用使用了来自 Android NDK 的原生 C/C++ 代码,或依赖于使用该代码的 SDK,您需要重新编译并可能调整代码以实现 16 KB 兼容性。好消息是:一旦应用更新为 16 KB 页面大小,同一应用二进制文件可无缝运行于 4 KB 和 16 KB 设备。
此表格描述了需要迁移并重新编译应用的场景
我们创建了多个 Android Studio 工具和指南,可帮助您为迁移至使用 16 KB 页面大小做好准备。
检测兼容性问题
APK 分析器:通过检查 lib 文件夹中的 .so 文件,轻松 识别 您的应用是否包含原生库。APK 分析器还可以直观地显示您的应用的 16 KB 兼容性。然后,您可以根据需要确定并更新库以符合 16 KB 要求。
对齐检查:Android Studio 还会在您的预构建库或 APK 不符合 16 KB 规范时发出警告。此时,您应使用 APK 分析工具查看哪些库需要更新,或是否需要进行代码修改。如果您希望在 CI(持续集成)管道中检测 16 KB 页面大小兼容性检查,可以 利用脚本和命令行工具。
Android Studio 中的 Lint 现在还会突出显示未对齐到 16 KB 的原生库。
采用 16 KB 对齐方式构建
工具更新:使用 16 KB 对齐重新构建(https://developer.android.com/guide/practices/page-sizes#build)您的原生代码。Android Gradle 插件(AGP)版本 8.5.1 或更高版本默认在打包过程中自动启用 16 KB 对齐(针对未压缩的共享库)。同样,Android NDK r28 及更高版本默认以 16 KB 对齐进行编译。如果您依赖其他原生 SDK,它们也需要以 16 KB 对齐。您可能需要联系 SDK 开发者以请求一个符合 16 KB 对齐要求的 SDK。
修复代码以实现页面大小无关性
消除硬编码假设: 识别并移除任何对 PAGE_SIZE 的硬编码依赖,或假设页面大小为 4 KB(例如 4096)。相反,应使用 getpagesize() 或 sysconf(_SC_PAGESIZE) 在运行时查询实际页面大小。
在 16 KB 环境中进行测试
Android 模拟器支持:Android Studio 提供了一个 16 KB 的 模拟器 目标(适用于 arm64 和 x86_64),直接在 Android Studio SDK 管理器中提供,允许您在上传到 Google Play 之前测试您的应用程序。
设备端测试:对于兼容设备(如Pixel 8和8 Pro及后续机型,从Android 15 QPR1版本开始),新增的开发者选项允许您在设备端测试时切换4 KB和16 KB的页面大小。您可以通过 adb shell getconf PAGE_SIZE 命令验证页面大小。
不要等待 – 立即准备您的应用
利用 Android Studio 的强大工具来检测问题、构建兼容的二进制文件、修复代码,并彻底测试您的应用以适应新的 16 KB 内存页大小。通过这样做,您将确保用户获得更好的体验,并为更高效的 Android 生态系统做出贡献。
如往常一样,您的反馈对我们至关重要——查看已知问题、报告 bug、提出改进建议,并加入我们的活跃社区: LinkedIn、Medium、YouTube 或 X 参与我们的活跃社区。
本文文字及图片出自 Transition to using 16 KB page sizes for Android apps and games using Android Studio
我曾与Unity的Android团队紧密合作,根据我的经验,将大型原生代码库迁移到新的页面大小时,往往会揭示出超越单纯替换硬编码常量(如PAGE_SIZE)的微妙运行时假设。我对Google的工具能提供很大帮助持乐观态度,但对它如何有效捕获这些更微妙的兼容性问题(如针对4K边界优化的自定义分配器或内存池)感到好奇。
我合作的一位同事在Arm架构上进行4k到64k页面过渡时遇到了各种问题。其中一些问题包括发现了一些在4k页面时并不明显或不重要的内存泄漏,但现在页面大小扩大了16倍,这些问题突然变得明显,甚至可能引发故障。
他们是否可以通过将页面大小设置为1MB等极大值来发现这些问题?
虽然有点跑题,但1MB页面其实并不算离谱。x86-64架构硬件支持4k、2MB和1GB页面大小(因为页面映射表的每一层都会从虚拟地址中截取9位)。幸运的是,它支持将这三种大小混合使用,因此通常你只需将大部分数据保存在4KB页面中,偶尔使用2MB/1GB页面。但据我所知,没有什么能阻止你强制所有用户空间代码使用2MB页面,尽管Linux内核不支持这一点。
很多软件这样做会无法正常工作。许多JIT编译器和内存分配器对页面大小有特定要求。此外,带标签的指针非常常见。
内存页面大小对带标签的指针(实际上对任何指针)应该是透明的,我不明白它们如何受到影响。如果你有一个对象位于地址0xAB0BA,底层页面的大小是否重要?
这可能涉及行为问题;例如,Redis建议在Linux中禁用透明巨大页面支持,因为(其中包括?)复制-写内存页面行为,如果你要将数据持久化到磁盘,仍然建议禁用。
1. 你有一个Redis实例,例如,1GB的映射内存在一个1GB的巨大页面中
2. 当 Redis 尝试将数据持久化到磁盘时,它会 fork 出一个自己的副本,以避免在写入时锁定整个数据集
3. 新的 Redis 进程对该 1GB 中的任何数据进行修改
4. 操作系统现在必须分配一个新的 1GB 页面并复制整个数据集
5. 哎呀,我们面临内存压力!最好将 1GB 数据写入分页文件,或从文件系统缓存中清除 1GB 数据,以便在接下来的 200 毫秒内分配这 1GB 页面。
你可以想象,那些试图通过智能分配内存来优化性能的内存分配器会如何关注这一点; 当自定义分配器试图分配大量小页面并将其保存在池中以便重复使用(无需向操作系统请求新页面)时,获得100个2MB页面而非100个4KB页面将造成巨大的内存浪费(以及潜在的性能损失)。
这并非意味着分配器一定会崩溃或出现异常行为(虽然可能发生),但往往会导致分配器声明“在这种条件下无法工作!”(或虽能工作但效率低下)。
此外,许多内核驱动程序会分配整页内存(有时被驱动的设备本身就有此要求)。
这只是又一个例子,说明带标签的指针是一个糟糕的主意,不应使用。总有一天,更多的地址空间会被使用,你的软件就会崩溃。
我理解谷歌希望开发者重新编译应用的意图,但我不明白为何要从应用商店移除旧应用……如果一个能正常运行的旧应用仅需一个4k页面,却浪费了12k空间,这又有什么关系?
谷歌已经毫无理由地从应用商店移除旧应用:
https://android-developers.googleblog.com/2022/04/expanding-…
你必须每年更新应用程序,即使只是无意义的版本更新。否则该应用将在两年后被移除。尽管谷歌声称此政策是为了确保用户安全,但近期多个Android版本并未包含任何重大安全更新。
我对Android不太熟悉,但Linux ELF二进制文件若指定4KB对齐,则无法在16KB页面大小的系统上运行,因为ELF解释器会拒绝加载此类文件。最近在尝试在具有16KB页面大小的Linux ARM系统上运行一个32位二进制文件时,我遇到了这个问题,因为32位OpenSSL库指定了4KB对齐。这可能是为了最大化ASLR可用的熵,但当页面大小增加时,这会破坏二进制文件。
无论如何,我假设Android也存在类似的问题。
作为一名未参与安卓或Linux开发的用户:我不在乎。修复它。你不能为了3%的性能提升而破坏整个未维护应用程序的生态系统。
我们多年来一直保持着Win32-x86可执行文件的兼容性。保持系统正常运行可能需要某种模拟层,这可能会对性能产生重大影响,但这没关系。我可以接受这一点。
“一切都停止工作”不是一个真正的操作系统可以接受的选项。我不希望把我的工作工具收起来,然后早上醒来发现工具箱制造商因为它们不符合新抽屉的效率要求而把它们送到了垃圾填埋场。
Android在家庭自动化领域是一个常见的例子,我绝对不推荐。你的灯开关是50年的投资。基于应用程序的灯开关在五年后还能正常工作的概率是50/50……更长时间的复合概率微乎其微。
我认为大多数应用程序都是用Java编写的,根据博客文章,它们不会受到影响。
只有用C++编写的应用程序需要编译,而这些应用程序很可能是大型游戏和对性能要求极高的应用程序。
工业设备会基于Android系统吗?没有ATM机、没有数控机床等。可以说,所有运行在Android上的设备都是一次性用品。直到最近,三星和谷歌才开始瞄准7年使用寿命。对于工业设备而言,7年可能还未还清贷款,更不用说这些设备的软件是否会更新了。
销售点系统,如Toast。飞机上的媒体系统。汽车中的信息娱乐系统。
事实上,NCR确实销售基于Android的ATM解决方案。[1]
Android实际上在需要为用户提供良好图形界面的嵌入式系统中被广泛使用。
[1]: https://www.zdnet.com/article/ncr-launches-kalpana-an-androi…
页面大小会影响页面权限;问题不在于浪费 12k,而是使用 4kb 页面时,你可以拥有一个连续的 8kb 区域,且该区域具有不同的权限。而 16kb 页面无法做到这一点,因为每次内存使用方式“错误”时都会导致段错误,试图透明地修复这个问题将是一场噩梦。
这是一个有效的观点,但内存保护难道不是更改页面大小后唯一对用户可见的影响吗?似乎大多数不使用写保护内存的应用程序都不会受到影响。
我认为最直接的问题是ELF段未对齐到16KB。代码会与数据相邻,无法添加间隔而不破坏ELF中的偏移量,这会在每次向可写代码开头的全局变量写入时,或执行代码段末尾的代码时引发段错误。
一个不太安全的选项是将权限设置为该区域的联合类型,因为代码很少依赖于权限的缺失。不过这将是一个相当严重的安全漏洞。
> 16KB页面无法在每次内存被“错误”使用时避免段错误,而试图透明地修复这个问题将是一场噩梦。
我原本以为内核可以捕获这种情况并实时重新映射,尽管这会以牺牲性能为代价。这种说法不正确吗,还是说性能太差以至于不值得这样做?
任何涉及内核以 4k 粒度跟踪权限但使用更大页面大小的情况,都将比使用 4k 页面更糟糕。
这取决于有多少程序实际上触发了故障场景,因此无法在抽象层面给出答案。
在最坏情况下,~每次内存访问都会导致内核需要修复,使得每次内存访问的性能下降几个数量级(例如,热缓存命中与捕获到内核、清空缓存相比,至少需要多出数百次访问)。
EDIT:我看到你建议重新映射页面权限。也许这有帮助!但也许它会将重新映射的成本加到最坏情况下,例如前4KB是写入第二个4KB的指令。
> 我能理解谷歌希望开发者重新编译应用的愿望,但我不认为有必要从应用商店中移除旧应用
可能是谷歌明确希望移除未维护或维护不足的应用。当然有些可能是干净的基本工具,可以一直正常运行,但很多可能是被遗弃的软件或粗制滥造的演示程序和Hello World应用,看起来老旧过时。他们最近已经进行了一次大规模清理。
随着谷歌和苹果努力结束其垄断地位,应用商店市场正在发生变化。也许他们看到了这种变化,并试图利用他们正在消亡的优势,将自己定位为精选且值得信赖的应用商店,提供高质量、维护良好且及时更新的应用程序。当应用程序可以从其他地方分发时,他们可以选择自己的客户。
他们已经迫使应用程序每隔几年就更新到新的API,这是阻止开发者继续使用过时功能的唯一方法。
期望使用4K页的应用程序将尝试在该粒度级别强制执行内存保护。
“但我看不到从应用商店中移除旧应用程序的必要性”
他们今年早些时候实际上移除了近50%的安卓应用,显然他们致力于提升质量和安全性
如果你的数据结构跨越了4K边界怎么办?
> 这张表描述了哪些应用需要过渡并重新编译
信息设计是我的热情
表格以图片形式添加,替代文本模糊地解释了表格内容且存在拼写错误。真棒。
从我这个新手角度来看,感觉大多数代码不应该关心页面大小?为什么需要重新编译?
更改页面大小时通常会导致哪些问题?
性能、安全性和I/O关键代码必须关注,因为页面大小影响TLB缓存,并且是安全标志(如只读、禁止执行等)的最小粒度,这些标志对例如保护页至关重要。
如果你的代码创建了两个守护页面来夹住一个安全关键页面,以确保在发生页面故障和崩溃时,假设边界在4KiB处,但实际上现在在16KiB处,这意味着缓冲区溢出现在将不会被捕获。
此外,由于性能原因,假设它在页面边界上的代码,现在只有25%的概率是这样。
这还意味着,原本预期包含在4KiB页面内的MMIO物理页面,当映射到敏感的用户空间驱动程序上下文时,相邻的MMIO控制块不会被触及,但现在可能也会受到影响,因为你可能会在任一方向上获得多达3个相邻块。这可能并不常见,我对Android内部实现了解不多,但仍然值得考虑。
这在很大程度上是因为在很多 C 代码中,PAGE_SIZE 是一个宏或常量,而不是在运行时根据代码运行的系统动态填充的值——我一直觉得这有点问题。
不过,如果使用 mmap() 等函数,硬编码 PAGE_SIZE 的代码根本无法运行,因为它会验证页面大小并在不匹配时报错。
无论如何处理,这都将引发一系列问题。
因为最终的ELF二进制文件是链接成包含页面对齐段的。段定义了二进制文件应如何加载到内存以及所需的权限。
如果你有一个4KB的段标记为读写,紧接着是一个读执行段,那么简单地加载它将引发一系列安全问题。
此外,许多平台数据结构(如动态可执行文件的全局对象表)依赖地址。你不能随意移动这些结构。
更糟糕的是,像C++标准库(或谷歌的Abseil)这样的库依赖页面大小来优化数据结构(如哈希表,即unordered_map)。
通常是低级代码和手动调整内存时假设页面大小。
一切正常,直到某个不常用的库突然发生段错误且没有错误信息
主要用于 I/O,例如 mmap 要求文件偏移量是页面大小的倍数。
就我所知:
如果你依赖于能够将内存范围标记为只读或可执行,现在你必须关注页面大小。如果你的代码仍然假设页面大小为 4KB,你可能会尝试更改页面子集的保护属性,结果要么无法实现预期效果,要么更改范围过大。这两种情况都会导致奇怪的故障。
这也会带来性能影响。例如,如果你之前使用mmap进行大量3.5KB的内存分配,每次分配4KB页面所导致的内存浪费可能并不严重。但现在这些3.5KB的分配将占用整个16KB页面,导致应用程序浪费大量内存。理想情况下,大多数应用程序不会直接使用mmap进行此类操作。我能想象这会给JIT编译器的作者带来麻烦。
一些算法也会利用页面大小来进行地址处理技巧。例如,如果你知道页面大小是4KB,地址’3’和’4091’都可以被确定为具有相同的保护标志(读/写/执行)并且是同一类型的内存(磁盘上的mmap文件、共享内存段、从GPU映射的内存等)。这将允许任何跟踪此类信息的表仅具有4KB的粒度,从而使表变得更小。因此,此类技巧也需要知道页面大小。
大多数代码不需要这样做,但你无法确定所使用的库在后台做了什么。对于少数需要关注此问题的代码,如果大量用户将其作为依赖项使用,情况可能会迅速变得非常复杂。
奇怪。据我所知,4K 和 64K 是 ARM64 架构中常见的页面大小,而 16K 则是苹果公司使用的那个“与众不同”的特殊页面大小。在 Linux 内核文档中没有提到 16K:
https://www.kernel.org/doc/html/next/arm64/memory.html
64K开始显得有些浪费。虽然性能提升有限,但粒度降低意味着内存浪费显著增加
在内存有限的手机上,这种权衡很快变得不利。16K相较于经典的4K页面大小是一个合理的提升。
AMD 在 Zen1 中将 PTE 融合系统从 8 页切换到 16K(4 个 4K 页),该系统本质上是对具有连续地址的页面表条目进行类似运行长度压缩的操作,将其合并到一个 TLB 槽中。
Raspberry Pi 5 发布的 64 位内核使用 16KB 页面。
16KB 在实际应用中确实是个特殊情况,但 ARM 表示这属于实现定义。
https://developer.arm.com/documentation/101811/0104/Translat…
如果你正在进行迁移,你真的应该追求完全可变的页面大小,否则五年后将会出现 64K 页面大小的 CPU,突然间每个人都必须重新编译一切,又会出现另一个兼容性壁垒…
有这样的东西吗?页面大小会被硬编码到可执行文件布局中,以及任何使用 PAGE_SIZE 常量(而非 sysconf(_SC_PAGESIZE))的地方。
确实需要重新设计许多内容才能使运行时可变页面大小成为选项。
4 KiB 页面大小自 20 世纪 60 年代以来一直被使用。更多的内存并不一定意味着更大的页面更有益。也许 16 KiB 对 Android 更好?也许。对于现代架构而言,最佳页面大小究竟应为多少,目前尚无明确共识。
> 自2025年11月1日起,所有提交至Google Play的针对Android 15及以上设备使用原生C/C++代码的新应用及应用更新,必须支持16 KB页面大小。
我明白大多数应用无需进行修改,重新编译即可,但对于需要代码修改的应用,这个时间框架是否足够?
他们之前提过这个要求,我看到的最后一篇帖子是今年五月初的。
他们仅在2024年8月于Android 15中添加了该支持。https://android-developers.googleblog.com/2024/08/adding-16-…
我不清楚“针对 Android 15+”的具体含义。这是否包括 API 级别较低的版本?
> 我不清楚“针对 Android 15+”的具体含义。这是否包括 API 级别较低的版本?
– 在 Android 系统中,应用程序在构建时会将 targetSdkVersion 设置为应用程序编译和测试所针对的 API 版本,但您可以将 minSdkVersion 设置为应用程序可运行的最低设备 API 版本。
– 在 API 级别高于 targetSdkVersion 的设备上,系统会查看应用程序的 targetSdkVersion,并禁用高于应用程序所针对的 API 版本的新功能。因此,应用程序应能在较新设备上正常运行。
– 在 API 级别低于 targetSdkVersion 但高于(或等于)minSdkVersion 的设备上,应用程序需自行检测缺失的 API,并在使用前进行适配以适应较旧的环境。
– 在 API 级别低于 minSdkVersion 的设备上,应用程序将无法运行。这确保用户获得明确的失败提示,而非因应用尝试调用缺失 API 导致的不可预测崩溃或异常行为。
因此,原则上可以构建一个针对最新 Android 15 版本的应用,同时兼容所有版本的 Android 系统,直至版本 1。针对 16 kiB 页面对齐进行链接的应用也应能在使用 4 kiB 页面的旧设备上运行。
Google Play 商店要求 targetSdkVersion 需与最新 Android 版本保持较近距离。但对 minSdkVersion 没有具体要求。
Android 应用在清单文件中有一项标记,用于告知系统“该应用是针对 Android X(API 级别 X)开发的”。
这使操作系统能够有选择地启用向后兼容性并更改某些行为(例如有选择地强制执行新权限,以避免旧应用程序出现故障)。
Play 商店要求应用程序在操作系统发布后的一定时间内(通常约 2 年)支持新操作系统并迁移 API。
此举旨在防止应用程序针对旧版操作系统以规避新的安全和隐私增强功能(例如请求显示通知的权限、请求访问麦克风的权限、允许显示全屏弹出广告等。这些限制均通过目标版本检查进行控制。)
如果你自己正在尝试构建原生代码,只需升级NDK(当你升级Android Gradle插件时,NDK会自动升级),因此这基本上是一个自动步骤(前提是你没有使用旧的AGP7构建脚本)。
如果你依赖于使用原生库的包,你需要等待他们更新。或者你分叉代码库,升级AGP并重新构建。
这是一个非常小的更改,除非你依赖于未维护的代码。
Linux 不支持多种基础页面大小,这有点遗憾。
“提供改进的性能提升”是一个冗余表达。“提供性能提升”就足够了。
根据在 Fedora 和 RHEL 上实现 aarch64 架构 64K 页面大小的经验,这将不会是一个简单的过渡。各种问题将以微妙、奇怪且有趣的方式出现。祝 Android 团队好运 🙂
我想你指的是被迫切换应用的“Android 开发者”。
这很愚蠢。抽象层级错了。
应用程序应假设页面大小为1字节。应能够以字节为单位映射、保护等内存范围——这是计算机中其他一切的粒度。程序员少一件需要担心的事。历史表明,伴随持续复杂性的性能优化通常无法持久(例如交错视频)。
在硬件层面,与其将地址的某些位作为页面大小,不如使用多个页面表和多个TLB缓存——例如,一个用于1兆字节页面,一个用于4千字节页面,一个用于单个字节页面。硬件会同时检查所有表(硬件中的并行处理成本很低!)。
这种设计的优势在于,假设进程地址空间中绝大多数字节由大型映射组成,你可以将更多映射放入(分割后的)TLB中——这会带来更好的性能,同时仍能实现精确的字节级保护。
操作系统是唯一存在复杂性的地方——它必须找到一种方法,将应用程序所需的映射适配到硬件能够处理的范围(例如,123456字节可能被转换为30个4千字节页面和576个字节页面)。
你对一个以性能提升为动机的变更的回应,是建议切换到一个性能会灾难性下降的方案?
在类似功耗和硅片面积下,分层TLB的性能可能更好,因为相同数量的晶体管下,分层TLB的命中率更高。
晶体管并非免费(涉及功耗、热量等),将它们浪费在实现1字节粒度的TLB上可能难以被接受,即使假设所有操作都能并行执行。
如果你要走那么远,不如将malloc()移至硬件并开始使用ARM风格的带标签安全指针。这样C用户终于可以摆脱内存分配错误。
数十年的内核开发、数十个操作系统、数十种物理架构,都已将4KB页面作为性能与内存使用之间的最佳平衡点,却被一个对情况一无所知的随口评论一笔抹去。这才是HN。
仅单字节页面的TLB内存使用和性能影响,就足以让CPU性能倒退到石器时代。
完全错误。4KiB页面大小源自一台总内存为512KiB的机器(1962年Atlas,3072B页面,96k 48b字)。由于惯性原因,它从未进行过扩展,且存在真实可测量的成本。64 KiB会是更好的选择,但我认为16比4更好。
因此有“最小”这一说法。该讨论实际上是关于Android编译时使用16KB页面,而CPU对更大页面的支持已显著提升,大多数消费级CPU可轻松支持4MB页面。
低于 4KB 的页面大小纯粹是浪费内存和性能。
我的设计方案包含多种页面大小——软件开发者完全可以将所有映射设置为 4KB 的倍数,而无需使用字节级页面。
我的示例是1MB、4KB和1字节页面——但实际设计可能会使用所有2的幂次或所有偶数2的幂次,以充分利用TLB空间。
此前未实现的原因在于鸡生蛋还是蛋生鸡的问题。CPU设计师不构建该功能,因为没有操作系统具备使用它的能力;而操作系统不使用它,因为没有CPU支持它。这对双方来说都是一项巨大的工作量。
> 应该能够以字节为单位映射、保护等内存范围——这是计算机中其他一切的粒度。
但你可以做到这一点,你只需要承担每字节保护内存时使用 PAGE_SIZE 的成本?