iOS Memory Deep Dive

前言


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容