美文网首页WWDC2018iOS Developer程序员
WWDC2018 - iOS Memory Deep Dive

WWDC2018 - iOS Memory Deep Dive

作者: fruitymoon | 来源:发表于2018-06-19 21:34 被阅读175次

    前言


    这个topic主要介绍了如何分析iOS app的内存占用和如何做内存优化,包括以下几部分,

    • 什么是内存占用
    • 怎么分析内存占用
    • 内存杀手---图像

    什么是内存占用(Memory Footprint)


    Not all memory is created equal.
    Memory page --- 内存管理中最小的单位。它是系统分配的,有可能一个page持有多个对象,也可能有些大的对象可以跨越多个pages。

    Memory Pages

    通常它是16KB大小,有三种类型的page。


    Memory Page Type
    Clean Memory

    一开始内存分配时的page都是干净的(堆里对象的分配除外),我们的app开始写入后才变dirty。从硬盘读进内存的文件,是只读的所以也是clean pages。

    clean memory
    Dirty Memory

    只要是app向内存写入了东西,就可以认为这个被写入的memory page变脏了。包括堆上的分配、解码的图片,动态库如果调用运行时的method swizzling也会让内存变脏,因为你的app提供了自己的实现。
    另外动态库的单例和类方法有助于帮助减少dirty memory,因为一直在内存中,系统不认为他们是dirty memory。

    dirty memory 这里给内存分配了一个可以装20000个int元素的数组,蓝色是clean pages,红色是dirty pages。比较疑惑为什么不是5个pages?
    Compressed Memory

    iOS并没有传统的swap操作,而是在iOS7引入了memory compressor(内存压缩器),对于一段时间没有使用的内存对象,内存压缩器会把对象压缩,释放出更多的pages。需要访问被压缩的对象时,内存压缩器再对它解压。

    所以app的运行内存 = pages number * page size;

    Memory Warnings

    • 并不总是由app导致(低端设备上有电话进来也可能导致警告)
    • 内存压缩器导致内存释放变得更复杂,可能会占用比原先不使用内存压缩器更多的内存。。。
    • 使用缓存要谨慎,建议用NSCache,而不是NSDictionary

    内存占用(Memory Footprint) = dirty memory + compressed memory。
    注意跟app的运行内存是两个概念,我们一般做内存分析时,就只需要分析内存占用。
    设备不同内存占用上限也不同,app通常上限较高,extension上限较低,超过上限会crash到EXC_RESOURCE_EXCEPTION

    Memory Footprint Limits

    分析Memory Footprint


    首先是debug navigator里的Xcode Memory Gauge,可以快速看到内存变化情况。
    当发现了有内存持续增长时,我们接下来可以使用instrument来分析,通常使用以下4种工具

    Allocations和Leaks就不介绍了,大家应该很熟悉。
    VM Tracker主要就是用来分析上面介绍过的dirty 和 compressed memory。swapped size在iOS里对应compressed memory size

    Virtual memory trace则提供了更详细的page输出日志,包括page cache hits and page zero fills

    现在当超过内存占用极限时,Xcode10会停在EXC_RESOURCE_TYPE_MEMORY断点,一个非常实用的功能,有助于接下来缩小分析内存溢出的范围,如下图,

    但实际上Instrument的分析工具跟后面要介绍的比起来并不是那么强大,接下来是重磅功能---memory graph,使用一系列强大的命令对这个文件操作,可以很容易发现内存问题。

    点击Debug Memory Graph -> File -> Export Memory Graph

    vmmap指令

    vmmap --summary App.memgraph
    vmmap --verbose App.memgraph | grep 'WebKit Malloc'
    

    注意Swapped Size显示的是压缩前的内存大小。
    应该重点关注Dirty Size 和 SwappedSize,他们加起来就是我们app的内存占用。
    一般通过--summary来初步定位dirty size大的Region。

    vmmap 指令和一下要介绍的指令都可以和linux命令,例如grep、awk结合使用
    grep命令:
    http://www.runoob.com/linux/linux-comm-grep.html
    awk命令:
    http://www.runoob.com/linux/linux-comm-awk.html
    以下是显示有多少由动态库导致的dirty pages

    $ vmmap -pages /Users/Documents/xxx.memgraph | grep '.dylib' | awk '{ sum += $6} END { print "Total Dirty Pages: " sum } '
    Total Dirty Pages: 1501
    

    leaks指令(感觉用处不大)

    leaks App.memgraph
    
    循环引用被标记出来了

    heap指令
    通常用来查看堆里的大对象的内存占用

    heap App.memgraph -sortBySize | grep 'AppName'
    

    进一步,-addresses all 可以看到对象的内存地址
    比如 heap App.memgraph -addresses SDWebImageCombinedOperation

    targeted heap objects

    如果想进一步看到该对象的调用栈,需要在scheme把Malloc Stack打开


    重新生成memgraph后,执行

    malloc_history App.memgraph --fullStacks [address]  
    
    backtrace

    应该根据你的需求,选择相应的内存分析命令。
    如果你想知道对象创建的过程,使用malloc_history;
    想知道对象间的引用关系,使用leaks;
    想知道对象的大小或数量,使用vmmap & heap;

    Images (图像是iOS里的内存杀手)


    Memory use is related to the dimensions of the image, not the file size.
    590KB的图片解码后占用了10MB内存

    iOS里的图像格式有许多种,从每像素1字节的格式到每像素8字节的格式都有,通常是默认的每像素4字节的SRGB

    多种图像格式,适用于各种场景

    使用UIGraphicsBeginImageContextWithOptions,会固定创建SRGB图像,每像素占用4字节。
    如果你最低支持iOS10,可以考虑使用UIGraphicsImageRenderer(iOS10以上),因为有些场景可能不需要使用SRGB,并且iOS12这个方法会自动选择最合适的图像格式

    结合新的api和tintColor,对于纯色图像,因为每像素只用了1字节,相比旧api可以减少75%的内存占用

    下采样

    别使用UIImage的drawInRect相关方法,而应该使用imageIO来压缩图片


    ImageIO使用示例

    后台优化
    unload large resources you cannot see
    就是退到后台或view消失时从内存中移除图片,进入前台或view出现时再加载图片

    总结


    • 内存是有限并且共享的一种资源,当某个app占用内存较大,别的app能获得的内存就越少,系统可能会把别的app干掉,腾出空间给当前运行的app,这样别的app再打开时就是冷启动了。所以为了让大家的app都能在内存里停留更长时间,我们应该时刻关注自己app的内存占用,维护一个良好的内存使用环境
    • 使用memory graph和多种命令行工具来分析内存占用
    • 选用合适的图像格式,iOS10以上使用UIGraphicsImageRenderer
    • 使用ImageIO压缩图像
    • 退到后台(不显示时)unload大的图片资源

    最后总结一下常用的内存分析的步骤

    • scheme里勾选malloc history
    • 死命折腾app并输出多个memgraph
    • vmmap --summary app. memgraph
    • 找大的dirtySize/swappedSize对应的region type
    • vmmap --verbose App.memgraph | grep 'some region',选取几个起始内存地址
    • malloc_history App.memgraph --fullStacks [address],观察backtrace,一般就能定位到我们app的某个方法

    视频地址:
    https://developer.apple.com/videos/play/wwdc2018/416/

    相关文章

      网友评论

        本文标题:WWDC2018 - iOS Memory Deep Dive

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