WWDC2018iOS Developer程序员

WWDC2018 - iOS Memory Deep Dive

2018-06-19  本文已影响175人  fruitymoon

前言


这个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

内存占用(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出现时再加载图片

总结


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

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

上一篇下一篇

猜你喜欢

热点阅读