IOS——Memory 温故而知新
首先说明一下写这篇文章的目标:
- 1.希望通过对内存更深入的理解解决一些内存相关的crash(研究一下腾讯开源的OOMDetector)
- 2.对内存相关的知识做一个总结笔记
- 3.温故而知‘错’ 温习的过程中也许会发现以前的一些理解上的错误 及时改正
iOS 内存机制
对于我们的 App 所依赖的设备而言,内存资源是有限的。降低 App 所使用的内存可以提高性能和体验,相反,过大的内存占用可能会导致 App 被系统强制退出。所以每个 iOS 开发者都应该关注内存问题。我们需要明确的是,这里的减少内存指减少 iOS App 的虚拟内存(Virtual Memory) 占用。
Pages Memory
首先理解一下内存页的概念,现代操作系统将内存划分为页,来简化内存管理,一个页其实就是一段连续的内存地址的集合,通常有4k和16k(iOS 64位是16K)的,成为 Virtual Page 虚拟页。与之对应的物理内存被称为Physical Page 物理页。内存页按照各自的分配和使用状态,可以被分为Clean
和 Dirty
两类。
-
当这些内存页开辟出来的时候,它们都是 Clean 的
-
当向处于某一页的内存写入数据时,这一页内存会变成 Dirty。
-
如果是只读的文件,它所占用到的内存页是 Clean 的。 除了以上两种,iOS7开始,系统开始采用压缩内存的办法来释放内存空间,当内存吃紧的时候,系统会将不使用的内存进行压缩,直到下一次访问的时候进行解压,被压缩的内存称为
Compressed Memory
。 总结: 1.Clean - Data that can be paged out of memory 指的是能够被系统清理出内存且在需要时能重新加载的数据,包括: -
Memory mapped files
-
Frameworks 中的 __DATA_CONST 部分
-
应用的二进制可执行文件
2.Dirty - Any memory that has been written to by your app 指的是不能被系统回收的内存占用,包括
- 所有堆上的对象
- 图片解码缓冲数据(Decoded image buffers)
- Frameworks 中的 __DATA 和 __DATA_DIRTY部分
3.Compressed Memory本质上也是Dirty memory。
所以我们开发人员可以去优化减少的内存占用就是Dirty和Compressed两部分
Tools for profiling footprint
为了更好的找到可以优化的内存占用,Xcode提供了一系列工具帮助开发人员进行debug
- Xcode memory gauge
- Allocations
- Leaks
- VM Tracker
- Debug memory graph
Debug memory graph
虽然可视化工具已经能够直观的表现我们想要了解的内存占用信息,但是在终端中不仅可以灵活地利用各种命令和 flag 突出我们想要的内容,更可以快速的实现信息查找和文本化交互。其实我还是本着对未知工具体验尝试的目的,去学习一下
- 通过Xcode配置(Product —> Scheme — > Edit Scheme)
- 开启内存图调试器
- 导出memgraph 文件 File->Export Memory Graph
- vmmap 命令 查看详细报告
vmmap /Users/suanle/Desktop/BWCMTApp.memgraph
查看摘要报告
vmmap --summary /Users/suanle/Desktop/BWCMTApp.memgraph
查看vmmap更多使用文档
man vmmap
查看内存泄漏
leaks /Users/suanle/Desktop/BWCMTApp.memgraph
查看leaks更多使用文档
man leaks
查看堆区内存
heap /Users/suanle/Desktop/BWCMTApp.memgraph
查看内存分配历史
malloc_history /Users/suanle/Desktop/BWCMTApp.memgraph
iOS 内存管理
iOS中一个APP就是一个进程,开发人员需要关注的内存管理,比如MRC,ARC,实际上都属于进程内部的内存管理,对于开发人员大多数时候还是从语言层面(代码层面
)对内存进行操作。
引用计数
引用计数方式最基本的形态就是让每个被管理的对象与一个引用计数器关联在一起,该计数器记录着该对象当前被引用的次数,每当创建一个新的引用指向该对象时其计数器就加1,每当指向该对象的引用失效时计数器就减1。当该计数器的值降到0就认为对象死亡。每个计数器只记录了其对应对象的局部信息——被引用的次数,而没有(也不需要)一份全局的对象图的生死信息。由于只维护局部信息,所以不需要扫描全局对象图就可以识别并释放死对象;但也因为缺乏全局对象图信息,所以无法处理循环引用的状况。更高级的引用计数实现会引入“弱引用”的概念来打破某些已知的循环引用。
总结:引用计数的优点:对象生命周期结束时,可以立刻被回收,而不需要等到全局遍历之后再回收 缺点:循环引用,所以需要开发人员在编码的时候格外注意
OOM (Out of Memory Crash)
Jetsam 机制
iOS 是一个从 BSD 衍生而来的系统,其内核是 Mach。其中内存警告,以及 OOM 崩溃的处理机制就是 Jetsam 机制,也被称为 Memorystatus。Jetsam 会始终监控内存整体使用情况,当内存不足时会根据优先级、内存占用大小杀掉一些进程,并记录成 JetsamEvent。 这里可以查看apple 开源的内核代码
触发didReceiveMemoryWarning
而如何监控内存警告,以及处理 Jetsam 事件呢?首先,内核会调起一个内核优先级最高的线程 这个线程会维护两个列表,一个是基于优先级的进程列表,另一个是每个进程消耗的内存页的列表。与此同时,它会监听内核 pageout 线程对整体内存使用情况的通知,在内存告急时向每个进程转发内存警告,也就是触发 didReceiveMemoryWarning 方法。
触发Out of Memory Crash
而杀掉应用,触发 OOM,主要是通过 memorystatus_kill_on_VM_page_shortage,有同步和异步两种方式。同步方式会立刻杀掉进程,先根据优先级,杀掉优先级低的进程;同一优先级再根据内存大小,杀掉内存占用大的进程。而异步方式只会标记当前进程,通过专门的内存管理线程去杀死。
检测OOM
OOM 分为两大类,Foreground OOM / Background OOM,简写为 FOOM 以及 BOOM。而其中 FOOM 是指 app 在前台时由于消耗内存过大,而被系统杀死,直接表现为 crash。
腾讯开源的 OOMDetector,通过 malloc/free 的更底层接口 malloc_logger_t 记录当前存活对象的内存分配信息,同时也根据系统的 backtrace_symbols 回溯了堆栈信息。之后再根据伸展树(Splay Tree)等做数据存储分析。
OOM常见原因
- 循环引用 比较容易出现互相引用的地方是block里使用了self,而self又持有这个block,只能通过代码规范来避免。
- UIWebView 无论是打开网页,还是执行一段简单的js代码,UIWebView都会占用APP大量内存。而WKWebView不仅有出色的渲染性能,而且它有自己独立进程,一些网页相关的内存消耗移到自身进程里,最适合取替UIWebView。
- autoreleasepool 通常autoreleased对象是在runloop结束时才释放。如果在循环里产生大量autoreleased对象,内存峰值会猛涨,甚至出现OOM。适当的添加autoreleasepool能及时释放内存,降低峰值。
- 大图片 大视图渲染
我们的照片主要有四类来源
- Image Assets
- Bundle,Framework 里面的图片
- 在 Documents, Caches 目录下的图片
- 网络下载的数据
如果我们想要自己创建Image Buffers,苹果的建议是使用UIGraphicsImageRenderer,因为它的性能更好,还支持广色域。这个东西简单了解了一下,性能虽好,但是实用性差,平时开发中需要自己创建image的场景很少,所以就不多介绍了。
留一个思考问题:对于 iOS 系统而言,绝大部分场景下哪类数据占内存最多呢?当然是图片!所以对于图片的使用,我们有哪些可以优化内存占用的方案呢???
作为一个开发者,有一个学习的氛围和一个交流圈子特别重要,这是我的交流群(123),大家有兴趣可以进群里一起交流学习!
收录:原文地址