简介:文章介绍了百度app ios端内存优化的原理,文章较长,分三次介绍。
iOS系统架构可分为内核驱动层(Kernel and Device Drivers Layer)、核心操作系统层(Core OS )、核心服务层(Core Services layer)、媒体层(Media layer可触摸层&应用层(Cocoa&Application layer),内核驱动层就是我们经常提到的Darwin,Darwin是苹果公司于2000年发布的一个开源操作系统,是由XNU和一些其他的Darwin库组成,XNU是由苹果公司发布的操作系统内核,XNU包含三部分:Mach、BSD、I/O Kit。
Mach是一个由卡内基梅隆大学开发的计算机操作系统微内核,是XNU内核,是作为 UNIX 内核的替代,主要解决 UNIX 一切皆文件导致抽象机制不足的问题,为现代操作系统做了进一步的抽象工作。Mach 负责操作系统最基本的工作,包括进程和线程抽象、处理器调度、进程间通信、消息机制、虚拟内存管理、内存保护等。在iOS系统架构中,内存管理是由在Mach层中进行的,BSD只是对Mach接口进行了POSIX封装,方便用户态进程调用。
页是内存管理的基本单位, 在 Intel 和 ARM 中,通常为4K,常用的查看虚拟内存的命令:hw.pagesize 查看默认页面大小; vm_page_free_count:当前空闲的 RAM 页数;vm_stat(1) - 从系统范围的角度提供有关虚拟内存的统计信息。
在 iOS ARM64机型中page size是16K,在 JetsamEvent 开头的系统日志里pageSize 代表当前设备物理内存页的大小。
手机自带的磁盘空间也很小,属于珍贵资源,同时跟桌面硬件比起来,手机的闪存 I/O 速度太慢,所以iOS系统没有交换空间;对于Mac系统,参考 Apple 官方文档About the Virtual Memory System,Mac 上有交换空间有换页行为,也就是当物理内存不够了,就把不活跃的内存页暂存到磁盘上,以此换取更多的内存空间。
内存压缩技术是从 OS X Mavericks (10.9) 开始引入的 (iOS 则是 iOS 7.0 开始),可以参考官方文档: OS X Mavericks Core Technology Overview, 在内存紧张时能够将最近使用过的内存占用压缩至原有大小的一半以下,并且能够在需要时解压复用。简单理解为系统会在内存紧张的时候寻找 inactive memory pages 然后开始压缩,达到释放内存的效果,以 CPU 时间来换取内存空间,NSPurgeableData是使用该技术典型的数据结构。所以衡量内存指标一定要记录 compressed内存 ,另外还需要记录被压缩的 page 的信息。
经过前面的内存压缩环节后,设备可用内存若仍处于危险状态,iOS系统需要各个App进程配合处理,会向各进程发送内存报警要求配合释放内存,具体来说,Mach内核系统的vm_pageout 守护程序会查询进程列表及其驻留页面数,向驻留页面数最高的进程发送NOTE_VM_PRESSURE ,被选中的进程会响应这个压力通知,实际表现就是APP收到系统的didReceiveMemoryWarning 内存报警,释放部分内存以达到降低手机内存负载的目标。
在收到内存报警时,App降低内存负载,可以在很大程度上避免出现OOM,具体源码分析见第三节。
当进程不能通过释放内存缓解内存压力时,Jestam机制开始介入,这是iOS 实现的一个低内存清理的处理机制,也称为MemoryStatus,这个机制有点类似于Linux的“Out-of-Memory”杀手,最初的目的就是杀掉消耗太多内存的进程,这个机制只有在iOS系统有,在Mac系统是没有的。系统在强杀 App 前,会先做优先级判断,那么,这个优先级判断的依据是什么呢?
iOS 系统内核里有一个数组,专门用于维护线程的优先级。这个优先级规定就是:内核线程的优先级是最高的,操作系统的优先级其次,App 的优先级排在最后,并且,前台 App 程序的优先级是高于后台运行 App 的,线程使用优先级时,CPU 占用多的线程的优先级会被降低。
Mach虚拟内存这一层完全以一种机器无关的方式来管理虚拟内存,这一层通过vm_map、vm_map_entry、vm_objec和vm_page四种关键的数据结构来管理虚拟内存。
第一、vm_map:表示地址空间内的多个虚拟内存区域。每一个区域都由一个独立的条目vm_map_entry表示,这些条目通过一个双向链表vm map links维护,参考XNU开源代码(https://opensource.apple.com/source/xnu/),代码路径:osftnk/vm/vm_map.h,我们可以清楚地看到vm_map结构。
第二、vm_map_entry:这个数据结构向上承接了vm_map,向下指向vm_object,该数据结构有很多权限访问标志位,任何一个vm_map entry都表示了虚拟内存中一块连续的区域,每一个这样的区域都可以通过指定的访问保护权限进行保护,在XNU源代码路径(osftnk/vm/vm_map.h)可看到具体数据结构定义。
第三、vm_object:这是一个核心数据结构,将前面介绍的vm_map_entry与实际内存相关联,该数据结构主要包含一个vm_page的链表、memory object分页器、标志位(用来表示底层的内存状态如初始化、已创建、已就绪或pageout等状态)和一些计数器(引用计数、驻留计数和联动计数等),XNU源代码路径:osfmk/vm/vm_object.h;
第四、vm_page: 重点包含offset偏移量和很多状态位:驻留内存、正在清理、交换出、加密、重写、和脏等,XNU源代码路径(osftnk/vm/vm_page.h)。
XNU内存管理的核心机制是虚拟内存管理,在Mach 层中进行的,Mach 控制了分页器,并且提供了各种 vm 和 mach_vm 消息接口。
Mach内核是按照page size大小来分配的内存的,对于苹果的arm64机型来说的page size是16K大小,但是我们通常在应用程序中在堆上申请内存的时候,单位都是字节,很显然内核提供的函数不适合直接提供给上层使用,这儿存在一个GAP,在iOS系统中libsystem_malloc.dylib就是用来弥补GAP的。
libsystem_malloc.dylib是iOS内核之外的一个内存库,开源地址:https://opensource.apple.com/source/libmalloc/。
当我们App进程需要的创建新的对象时,如调用[NSObject alloc],或释放对象调用release方法时,请求先会走到libsystem_malloc.dylib的malloc()和free()函数,然后libsystem_malloc会向iOS的系统内核发起内存申请或释放内存,具体来说就是调用操作系统Mach内核提供的内存分配接口去分配内存或释放内存,苹果操作系统Mach内核提供了如下内存操作的相关接口。
libsystem_malloc就是通过mach_vm_allocate和mach_vm_map来申请page size整数倍大小的内存,然后缓存这些内存页,形成一个内存池。当malloc调用的时候,可以根据传入的size大小来应用不同的分配策略,从这些缓存的内存中,分配一个size大小的内存地址返回给上层调用,同时记录这次分配操作的元数据。当调用free的时候,可以根据分配的地址,找到元数据,进而回收分配的内存。