十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
我们知道,手机的内存是有限的,如果应用内存占用过大,轻则引起卡顿,重则导致应用崩溃或被系统强制杀掉,更严重的情况下会影响应用的留存率。因此,内存优化是性能优化中非常重要的一部分。但是,很多开发者对内存的认识还停留在应用开发这一层,平时只是参考网上的方案,对内存进行比较浅显的优化。想要深入进行内存优化,我们需要从操作系统的层面了解内存是怎么管理的,又是如何被使用的。
成都创新互联专业为企业提供温州网站建设、温州做网站、温州网站设计、温州网站制作等企业网站建设、网页设计与制作、温州企业网站模板建站服务,十多年温州做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。可能会有人疑问:“为什么做个内存优化需要从操作系统层了解内存呢?”我们确实可以在网上搜到很多内存优化的文章,但它们都是从上层应用出发进行优化的,而不同的应用因为环境不一样、业务不一样,很多优化方法都不能通用。因此,只有当我们从底层掌握了内存的原理,从下而上地制定优化方案,才能适用于任何业务,甚至当我们转型到 iOS、前端或者后端都能通用。
接下来,我们就从操作系统底层出发,重新认识内存。
我们先将目光放到操作系统的早期,在这个环境下,程序都是直接操作物理内存的。比如一个程序执行如下指令:
MOV REGISTER1,0
计算机会将位置为 0 的物理内存中的内容移到 REGISTER1 的寄存器中。在这种情况下,如果第二个程序在 0 的位置写入一个新的值,就会擦掉第一个程序存放在相同位置上的所有内容,导致第一个程序崩溃。
正因为应用程序可以直接操作物理内存,所以我们完全可以修改其他程序在内存中的数据,导致程序崩溃或者产生安全问题。因此,对当时的操作系统来说,同时运行多个程序很困难。
为了解决这个问题,我们自然而然会想到:不允许应用程序直接操作物理内存。于是虚拟内存的技术诞生了。
为了更好地了解什么是虚拟内存,我们先看看早期直接操作物理内存系统下的内存模型长什么样。从下面内存模型的简化图中我们可以看到,物理内存中存在两块数据,一个是操作系统的数据,一个是应用程序的数据。除此之外,其实还会有设备驱动程序的数据,它们不是我们了解的重点就先不列上去了。
什么是虚拟内存?虚拟内存技术相当于给每个程序一个独占且连续的内存,比如 32 位系统下是 4G(2^32),只不过这个内存是虚拟的。同时,虚拟内存需要能够映射到真实的物理内存。简化的内存模型如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-haHe4vHL-1670148923898)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/edd00859a2094eaa964f8ce588df1f18~tplv-k3u1fbpfcp-watermark.image?)]
从上图的简化内存模型,我们可以看到,每个程序都可以独享一块虚拟内存。在 Linux 系统上,一个进程代表着一个程序,这里我们可以理解成每个进程都独享一块虚拟内存,这块虚拟内存在 32 位系统下是 4G(2^32),64 位系统下是 2^48,即 256TB(这里不是 2^64,是因为 256TB 已经足够大了,如果用 2^64,会有大量的寻址空间浪费)。
其次,虚拟内存都由应用程序和操作系统这两部分组成。其中,应用程序这部分虚拟内存是应用独占的,操作系统这部分虚拟内存则由所有进程共享,而所有进程的操作系统这部分虚拟内存都指向了同一段物理内存。虚拟内存到物理内存的映射由操作系统来实现,操作系统在做映射操作时会寻找可用的物理内存,不可能出现覆盖其他数据的情况,这让同时运行多个程序成为了可能。
上图的虚拟内存是一个简化的模型。实际上,虚拟内存和物理内存都是按照页来管理和映射的,一页的大小为 4KB,我们来看一个 32 位 Android 系统、物理内存为 2G 的设备的内存模型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ysAU5ah2-1670148923899)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/efb87e2370624ccdb09642d2018c0581~tplv-k3u1fbpfcp-watermark.image?)]
可以看到,物理内存和虚拟内存是通过 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);
mmap 函数有 2 种用法:
第 1 种用法可以让我们读文件的效率更高(比如 Android 读取 dex 文件就是通过 mmap 来提高读取速度),也可以用来实现数据跨进程传输(比如 Android 共享内存机制、Binder 通信都是通过 mmap 来实现的)。malloc() 函数使用了 mmap 函数的第 2 种用法,即在 Heap 区域中申请一块内存。
需要注意的,这里申请的内存都是虚拟内存,并且这个时候并不会分配真正的物理内存,只有当我们真正要往这块虚拟内存区域写入数据时,操作系统检查到对应的虚拟内存没有映射到物理内存,便会发生缺页中断,然后分配一块同样大小的物理内存,并建立映射关系。这是一种懒加载技术,也是内存优化的方案之一。
malloc() 函数在申请内存小于 128k 时会使用 sbrk() 函数,sbrk() 会将堆顶指针(即前面提到的 brk)向高地址移动,获得新的虚存空间,这些策略都是基于性能考虑的。比如 Android 虚拟机在分配大对象时,也会专门放在 LargeObjectSpcace 中,这些就不展开讲了。至于 Linux 系统是如何发生缺页中断,如何分配物理内存,如何建立映射关系的,都属于 Linux 系统相关知识了,更详细的知识点会在后面的篇章中结合实战项目穿插着讲解。
小结事实上,直接操作物理内存的操作系统并没有消失,我们现在的嵌入式设备,如冰箱,微波炉等等都能直接操作物理内存。这其实也符合它们的使用场景,直接操作物理内存会让性能开销更小,操作也更方便。但需要同时运行多个软件的系统都有虚拟内存,可以说虚拟内存是现代操作系统最重要的发明之一了。
当我们重新认识了内存后,我们再来看内存优化,它其实分为两部分。
在后面的章节中,我会针对这两部分,总结出体系的优化方法论,再搭配讲解一些优化实践。
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧