内存优化之重新认识内存

📅 2026/8/27 12:36:11
内存优化之重新认识内存
我们知道手机的内存是有限的如果应用内存占用过大轻则引起卡顿重则导致应用崩溃或被系统强制杀掉更严重的情况下会影响应用的留存率。因此内存优化是性能优化中非常重要的一部分。但是很多开发者对内存的认识还停留在应用开发这一层平时只是参考网上的方案对内存进行比较浅显的优化。想要深入进行内存优化我们需要从操作系统的层面了解内存是怎么管理的又是如何被使用的。可能会有人疑问“为什么做个内存优化需要从操作系统层了解内存呢”我们确实可以在网上搜到很多内存优化的文章但它们都是从上层应用出发进行优化的而不同的应用因为环境不一样、业务不一样很多优化方法都不能通用。因此只有当我们从底层掌握了内存的原理从下而上地制定优化方案才能适用于任何业务甚至当我们转型到 iOS、前端或者后端都能通用。接下来我们就从操作系统底层出发重新认识内存。我们先将目光放到操作系统的早期在这个环境下程序都是直接操作物理内存的。比如一个程序执行如下指令MOV REGISTER1,0计算机会将位置为 0 的物理内存中的内容移到 REGISTER1 的寄存器中。在这种情况下如果第二个程序在 0 的位置写入一个新的值就会擦掉第一个程序存放在相同位置上的所有内容导致第一个程序崩溃。正因为应用程序可以直接操作物理内存所以我们完全可以修改其他程序在内存中的数据导致程序崩溃或者产生安全问题。因此对当时的操作系统来说同时运行多个程序很困难。为了解决这个问题我们自然而然会想到不允许应用程序直接操作物理内存。于是虚拟内存的技术诞生了。为了更好地了解什么是虚拟内存我们先看看早期直接操作物理内存系统下的内存模型长什么样。从下面内存模型的简化图中我们可以看到物理内存中存在两块数据一个是操作系统的数据一个是应用程序的数据。除此之外其实还会有设备驱动程序的数据它们不是我们了解的重点就先不列上去了。什么是虚拟内存虚拟内存技术相当于给每个程序一个独占且连续的内存比如 32 位系统下是 4G(2^32)只不过这个内存是虚拟的。同时虚拟内存需要能够映射到真实的物理内存。简化的内存模型如下从上图的简化内存模型我们可以看到每个程序都可以独享一块虚拟内存。在 Linux 系统上一个进程代表着一个程序这里我们可以理解成每个进程都独享一块虚拟内存这块虚拟内存在 32 位系统下是 4G(2^32)64 位系统下是 2^48即 256TB这里不是 2^64是因为 256TB 已经足够大了如果用 2^64会有大量的寻址空间浪费。其次虚拟内存都由应用程序和操作系统这两部分组成。其中应用程序这部分虚拟内存是应用独占的操作系统这部分虚拟内存则由所有进程共享而所有进程的操作系统这部分虚拟内存都指向了同一段物理内存。虚拟内存到物理内存的映射由操作系统来实现操作系统在做映射操作时会寻找可用的物理内存不可能出现覆盖其他数据的情况这让同时运行多个程序成为了可能。上图的虚拟内存是一个简化的模型。实际上虚拟内存和物理内存都是按照页来管理和映射的一页的大小为 4KB我们来看一个 32 位 Android 系统、物理内存为 2G 的设备的内存模型。可以看到物理内存和虚拟内存是通过 4K 大小的页一一对应的。这个时候如果我们再用文章最开头的那个指令MOV REGISTER1,0在虚拟内存技术的加持下此时计算机就不会直接将物理地址为 0 的内存移到 REGISTER1 寄存器了而是先寻找虚拟地址 0 对应的物理地址 4096然后将物理地址为 4096 的内容移到 REGISTER1 寄存器中MOV REGISTER1,4096虚拟地址转换成物理地址是由计算机的内存管理单元MMU完成的它属于硬件部分而不是系统软件部分所以转换速度很快。虚拟内存的内存模型知道了虚拟内存由操作系统和应用程序两部分组成并且虚拟内存都由页来维护和管理之后我们再深入了解一下 Linux 系统中虚拟内存的内存模型它需要和 Linux 系统的可执行文件也就是 ELF 文件一起配合来看。为了让你理解起来更简单这里的 ELF 文件格式也被我简化了后面用到的时候再进行深入介绍。在 Linux 系统中存放操作系统的虚拟内存区域被称为内核空间剩下的存放应用的虚拟内存区域称为用户空间。内核空间占用了 1G位于虚拟地址的高地址区域而 ELF 文件的一些数据是存放在低地址的区域即从地址 0 开始。下面我详细解释一下内存模型中的几个区域栈由编译器自动分配释放 存放函数的参数值局部变量的值等。堆动态内存分配可以由开发者自己分配和释放malloc 和 free 函数实现Android 开发时不需要我们手动分配和释放因为虚拟机程序已经帮我们做了。堆的开始地址由变量 start_brk 描述堆的当前地址由变量 brk 描述。BSS存放全局未初始化静态未初始化数据。数据段存放全局初始化静态初始化数据。程序代码区存放的是 ELF 文件代码段。可以看到栈内存的分配是从上到下的而堆内存的分配是从下到上的这种方式可以最大程度利用虚拟内存的空间。虚拟内存分配前面我们已经知道应用是没法直接操作物理内存的所以我们在开发 App 时分配的内存实际都是虚拟内存。那么我们怎么申请虚拟内存呢开发 Android 应用时并不需要我们自己去分配内存直接 new 一个对象声明一个变量或者常量即可也不需要我们自己去做释放但所有的数据都需要内存这些都是虚拟机帮我们做。虚拟机分配申请内存主要使用的是malloc() 函数它是 C 语言库的一个标准函数。void *malloc(size_t size)malloc 函数是一个 C 语言库的函数所以它分配内存最终还是得调用 Linux 系统提供的函数让 Linux 内核去帮我们申请一块内存。内核会调用 mmap() 函数在堆中分配我们想要的内存空间大小。 mmap() 函数是 Linux 系统一个很重要的函数我们需要深刻认识它。void *mmap(void *addr,size_t length,int prot,int flags,int fd, off_t offset);参数 addr 指向欲映射的内存起始地址通常设为 NULL代表让系统自动选定地址映射成功后返回该地址参数 length 表示将文件中多大的部分映射到内存参数 prot 指定映射区域的读写权限参数 flags 指定映射时的特性如是否允许其他进程映射这段内存参数 fd 指定映射内存的文件描述符参数 offset 指定映射位置的偏移量一般为 0。mmap 函数有 2 种用法映射磁盘文件到用户空间中匿名映射不映射磁盘文件而是向映射区申请一块内存此时的 fd 入参传 -1。第 1 种用法可以让我们读文件的效率更高比如 Android 读取 dex 文件就是通过 mmap 来提高读取速度也可以用来实现数据跨进程传输比如 Android 共享内存机制、Binder 通信都是通过 mmap 来实现的。malloc() 函数使用了 mmap 函数的第 2 种用法即在 Heap 区域中申请一块内存。需要注意的这里申请的内存都是虚拟内存并且这个时候并不会分配真正的物理内存只有当我们真正要往这块虚拟内存区域写入数据时操作系统检查到对应的虚拟内存没有映射到物理内存便会发生缺页中断然后分配一块同样大小的物理内存并建立映射关系。这是一种懒加载技术也是内存优化的方案之一。malloc() 函数在申请内存小于 128k 时会使用 sbrk() 函数sbrk() 会将堆顶指针即前面提到的 brk向高地址移动获得新的虚存空间这些策略都是基于性能考虑的。比如 Android 虚拟机在分配大对象时也会专门放在 LargeObjectSpcace 中这些就不展开讲了。至于 Linux 系统是如何发生缺页中断如何分配物理内存如何建立映射关系的都属于 Linux 系统相关知识了更详细的知识点会在后面的篇章中结合实战项目穿插着讲解。小结事实上直接操作物理内存的操作系统并没有消失我们现在的嵌入式设备如冰箱微波炉等等都能直接操作物理内存。这其实也符合它们的使用场景直接操作物理内存会让性能开销更小操作也更方便。但需要同时运行多个软件的系统都有虚拟内存可以说虚拟内存是现代操作系统最重要的发明之一了。当我们重新认识了内存后我们再来看内存优化它其实分为两部分。一是物理内存的优化也就是这个程序实际消耗的物理内存。二是虚拟内存的优化在前面我们也知道了 32 位机只有 3G 的虚拟内存可用所以一个比较大的Android 程序很容易就会出现虚拟内存不足的情况64 位系统就完全不用担心这个问题。在后面的章节中我会针对这两部分总结出体系的优化方法论再搭配讲解一些优化实践。作者helson赵子健链接https://juejin.cn/post/7173235529029271566最后如果想要成为架构师或想突破20~30K薪资范畴那就不要局限在编码业务要会选型、扩展提升编程思维。此外良好的职业规划也很重要学习的习惯很重要但是最重要的还是要能持之以恒任何不能坚持落实的计划都是空谈。如果你没有方向这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》帮大家将杂乱、零散、碎片化的知识进行体系化的整理让大家系统而高效地掌握Android开发的各个知识点。相对于我们平时看的碎片化内容这份笔记的知识点更系统化更容易理解和记忆是严格按照知识体系编排的。全套视频资料一、面试合集二、源码解析合集三、开源框架合集欢迎大家一键三连支持若需要文中资料直接点击文末CSDN官方认证微信卡片免费领取↓↓↓