美文网首页
iOS 内存监控与治理(二)-- iOS 内存机制与内存管理

iOS 内存监控与治理(二)-- iOS 内存机制与内存管理

作者: topws1 | 来源:发表于2021-10-20 19:52 被阅读0次

    上一章节我们认识了计算机中的内存相关知识,本章节我们整理一些 iOS中内存需要关注的点以及 iOS 的内存管理。

    关于虚拟内存系统

    iOS 包含一个完全集成的虚拟内存系统,这个虚拟内存系统是无法关闭的,会一直开启。这个虚拟内存系统可以为每个32位进程提供4GB的可寻址空间。

    虚拟内存管理器会创建一个逻辑地址空间(或虚拟地址空间)用于每个进程(APP),并将其划分为大小均匀的内存块,称为页。早期的版本,页面大小是4kb,A9系统之后,每页大小为16kb。MMU 负责虚拟地址和物理地址的转换。

    低内存警告

    当iOS 遇到内存不足时,系统可能会向当前运行中的APP发送低内存警告。APP在接收到低内存警告时,应该及时做出响应,尽可能的释放未在使用的内存对象。

    OOM

    如果在收到低内存警告时,未能释放足够多的内存资源,那么系统可能会杀死一个或多个APP,这就会产生所谓的OOM(out-of-memory)崩溃。oom发生时从现象上看,与普通的Crash 并无区别,只是无法像监控Crash那样监控到OOM崩溃。不过在发生OOM崩溃后,当前设备的设置-隐私-分析与改进中会生成 JetsamEvent开头的日志。

    iOS 内存占用

    当内存不足的时候,系统会按照一定策略来腾出更多空间供使用,比较常见的做法是将一部分低优先级的数据挪到磁盘上,这个操作称为 Page Out。之后当再次访问到这块数据的时候,系统会负责将它重新搬回内存空间中,这个操作称为 Page In

    然而对于移动设备而言,频繁对磁盘进行IO操作会降低存储设备的寿命。从 iOS7 开始,系统开始采用压缩内存的办法来释放内存空间,被压缩的内存称为 Compressed Memory。下面依次介绍一下 iOS App 通常情况下的三种内存类型:Clean MemoryDirty Memory以及Compressed Memory

    Clean Memory

    可以简单理解为能够被重新写入数据的干净内存。对开发者而言是read-only,而iOS系统可以写入或移除。

    1. System Framework、Binary Executable占用的内存

    2. 可以被释放(Page Out,iOS上是压缩内存的方式)的文件,包括内存映射文件Memory mapped file(如image、data、model等)。内存映射文件通常是只读的。

    3. 系统中可回收、可复用的内存,实际不会立即申请到物理内存,而是真正需要的时候再给。

    4. 每个framework都有DATA_CONST段,当App运行时使用到了某个framework,该framework对应的DATA_CONST的内存就由clean变为dirty了。

    注意:如果通过文件内存映射机制memory mapped file载入内存的,可以先清除这部分内存占用,需要的时候再从文件载入到内存。所以是Clean Memory。

    Dirty Memory

    Dirty Memory 是指那些被 App 写入过数据的内存,包括所有堆区的对象、图像解码缓冲区,同时,类似 Clean memory,也包括 App 所用到的 frameworks。每个 framework 都会有 _DATA 段和 _DATA_DIRTY 段,它们的内存是 Dirty 的。

    值得注意的是,在使用 framework 的过程中会产生 Dirty Memory,使用单例或者全局初始化方法是减少 Dirty Memory 不错的方法,因为单例一旦创建就不会销毁,全局初始化方法会在 class 加载时执行。

    Compressed Memory

    当内存吃紧的时候,系统会将不使用的内存进行压缩,直到下一次访问的时候进行解压。

    例如,当我们使用 Dictionary 去缓存数据的时候,假设现在已经使用了 3 页内存,当不访问的时候可能会被压缩为 1 页,再次使用到时候又会解压成 3 页。

    iOS 内存管理

    iOS 采用引用计数(Reference Count)作为对象的内存管理方式,在当前的Swift和OC工程中,默认使用ARC(Automatic Reference Count)。我们可以在 Build Setting:Apple Clang - Language - Objc查看当前APP是否打开了ARC。此外,在Build Phases:Compile Sources 中为单个文件配置是否打开ARC。

    引用计数

    引用计数是一种对象生命周期的管理方式。当创建一个对象时,它的引用计数初始化为1,如果有新的指针指向这个对象时,它的引用计数加一,当然如果这个指针不再指向这个对象,那么引用计数就会减一。当对象的引用计数为0时,系统将该对象销毁并回收内存。

    ARC与MRC

    我们知道早期的iOS 开发是需要研发人员自己手动管理引用计数的,也就是MRC。在2011年之后,苹果推出了自动引用计数 - ARC,ARC的原理是依赖编译器的静态分析能力,在编译时于合适的位置插入引用计数管理代码。

    虽然ARC能够帮助我们解决绝大部分场景下的内存问题,但是在实际研发中,还是有一些场景需要研发人员手动管理。

    • 循环引用
    • 与底层 Core Foundation 对象交互
    循环引用

    两个对象相互引用或者多个对象形成环状引用,都会产生循环引用,导致对象无法正常的释放。比如A控制器,持有一个Block - B,该B内又引用了A控制器的某个属性,这样就导致了A与B的循环引用。循环引用可以通过弱引用(weak reference) 的方式来打破循环引用。所谓弱引用就是持有对象的同时,且不增加引用计数。

    弱引用的实现原理

    弱引用的实现原理是这样,系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。

    从原理上看,弱引用的使用是有额外的开销,不建议在项目中无脑使用弱引用。

    与底层 Core Foundation 对象交互

    由于 Core Foundation 对象底层是通过MRC的方式来管理生命周期,所以我们在使用的过程中,还是需要自己手动管理。小技巧:底层的Core Foundation对象创建时大多是以XxxCreateXxx,XxxCreateWithXxx的方式使用的。

    // 创建一个 Core Foundation 对象
    CTFontRef src = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, nil);
    
    // 引用计数加 1
    CFRetain(src);
    // 引用计数减 1
    CFRelease(src);
    

    此外,在涉及到Core Foundation对象与OC对象转换时,需要引入bridge关键字。

    • __bridge: 只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
    • __bridge_retained:类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
    • __bridge_transfer:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。
      参考:

    https://juejin.cn/post/6844904056863850504

    https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MemoryAlloc.html#//apple_ref/doc/uid/20001881-107791

    相关文章

      网友评论

          本文标题:iOS 内存监控与治理(二)-- iOS 内存机制与内存管理

          本文链接:https://www.haomeiwen.com/subject/zcptaltx.html