iOS内存的深入探究(WWDC 2018 session 416)

概述

首先设备硬件资源是固定的,所以app的内存资源是有限的。较低的内存占用可以提高用户体验以及性能。如果内存占用过大,可能会被系统杀掉。所以每个开发者都应该注意内存问题。本session主要分为以下几方面:

为什么要减少内存占用

内存占用

分析内存占用的工具

图像

在后台时,对内存的优化

演示demo

为什么要减少内存占用?

答案很简单,为了更好的用户体验。减少内存占用能同时减少其对CPU时间维度上的消耗,从而不仅使您所开发的App,其他App以及整个系统也都能表现的更好。

内存占用

并非所有的内存占用都是相等的。而要减少的内存占用其实指的是虚拟内存(Virtual Memory) 占用。

Pages

内存是由系统管理,一般以页为单位来划分。 

在iOS 上,每一页包含16KB的空间。系统会按照页来分配内存,堆上可能会有多个对象在一页上,也可能一个对象占用多页。

所占用页总数乘以每页空间得到的就是这段数据使用的总内存。 

内存页按照各自的分配和使用状态,可以分为Clean和Dirty两类。

举个例子,如果我申请了一个20000个整型的数组(80000个字节)。系统可能会分配给我6页内存。

当我申请空间后,他们都是Clean的

如果我在数组的第一个位置写入数据,那么该页就会变Dirty了

如果我在数组最后一个位置写入数据,那么该页就会变Dirty了。

中间的几页都是Clean的,因为他们还未被写入。 

内存映射文件

当 App 访问一个文件时,系统内核会负责调度,将磁盘上的文件加载并映射到内存中。如果这是只读的文件,它所占用到的内存页是Clean的。 

如下图所示,一个50KB的图片被加载到内存中时,需要分配4页内存来存储。其中第四页中有2KB的空间会被用来存储这个图片的数据,剩余空间可能会被用来存储其它数据。前三页总是可以被系统清除的。 

典型app内存类型

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

Clean Memory

Clean Memory是指那些可以用以Page Out的内存,只读的内存映射文件,或者是App所用到的frameworks。每个frameworks都有_DATA_CONST段,通常他们都是Clean的,但如果用runtime进行swizzling,那么他们就会变Dirty。

Dirty Memory

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

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

Compressed Memory

由于闪存容量和读写寿命的限制,iOS 上没有Disk swap机制,取而代之使用Compressed memory。

Compressed memory是在内存紧张时能够将最近使用过的内存占用压缩至原有大小的一半以下,并且能够在需要时解压复用。它在节省内存的同时提高了系统的响应速度,特点总结起来如下: 

* Shrinks memory usage 减少了不活跃内存占用 

* Improves power efficiency 改善电源效率,通过压缩减少磁盘IO带来的损耗 

* Minimizes CPU usage 压缩/解压十分迅速,能够尽可能减少 CPU 的时间开销 

* Is multicore aware 支持多核操作

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

Memory Warning

内存警告,不一定总是应用自身导致的

内存压缩技术使得释放内存变得复杂 

缓存策略

小结

通常情况下,我们所说的内存占用是指Dirty Memory和Compressed Memory,Clean Memory不需要过多关心。

App 能使用比较多的内存空间,但是上限会根据设备不同而不同。Extension能使用的最大内存则要低很多,所以当你在开发Extension的时候尤其要注意内存使用。当使用的内存超出限制的时候,系统会抛出EXC_RESOURCE_EXCEPTION异常。

分析内存占用的工具

Xcode Memory Gauge

在Xcode中,你可以通过Memory Gauge工具,很方便快速的查看App运行时的内存情况,包括内存最高占用、最低占用,以及在所有进程中的占用比例等。如果想要查看更详细的数据,就需要用到Instruments了。

Instruments

在 Instruments 中,你可以使用Allocations、Leaks、VM Tracker和 Virtual Memory Trace对App进行多维度分析。

Allocations

Leaks

VM Tracker

Virtual Memory Trace

Debug Debugger - Memory Resource Exceptions

当你使用 Xcode 10以前的版本进行调试时,在内存过大时,debug session会直接终止,并且在控制台打印出异常。从Xcode 10开始,debugger会自动捕获EXC_RESOURCE RESOURCE_TYPE_MEMORY异常,并断点在触发异常抛出的地方,十分方便定位问题。 

Xcode Memory Debugger

Xcode Memory Debugger的内存调试器是在Xcode 8中提供的,它可以帮助您跟踪对象依赖性,周期和泄漏。在Xcode 10中,优化了界面布局。 

你也可以点击File->Export Memory Graph将其导出为memgraph文件,通过命令行对其进行分析。下面说下几个命令行工具

vmmap

vmmap 能够打印出进程信息,所有分配给该进程的 VM区域以及VM区域的种类、内存占用信息等内容。

利用--summary则能够根据不同的区域类型打印出详细的内存占用类型和信息。这里需要注意的是 SWAPPED SIZE在iOS上指的是Compressed memory size且其值表示压缩前的占用大小

vmmap--summaryApp.memgraph

1

如果您希望查看更多的信息,那么直接调用即可。您将获得所有区域的内容。

vmmap App.memgraph

1

配合管道命令查看所有动态库的Ditry Pages的总和

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

1

更多使用方式请查看vmmap的文档

man vmmap

1

Leak

顾名思义,就是查看内存泄漏的。

leaks xx.memgraph

1

更多使用方式也可以查看man手册。

heap

查看堆区内存

heap xx.memgraph

1

默认情况下,是按照数量排序的,当然也可以通过参数-sortBySize让其来按照大小排序。

heap xx.memgraph-sortBySize

1

排列之后,我们发现了一些巨大的NSConcreteData对象,通过下面的命令,就可以得到每个对象的内存地址。

heap xx.memgraph -addresses'NSConcreteData'#得到全部对象的内存地址#heap xx.memgraph -addresses all

1

2

3

4

有了这些地址呢,我们就可以知道他们是从哪里来的。有了这些对象的内存地址之后,我们还需要另一样工具帮助我们做下一步分析。

Enabling Malloc Stack Logging

在Product->Scheme->Edit Scheme->Diagnostics中,开启 Malloc Stack 功能,建议使用Live Allocations Only选项。 

之后lldb会记录调试过程中对象创建的堆栈,配合malloc_history工具,就可以定位到那些占用了过大内存的对象是哪里创建的。

malloc_history

查看内存分配的历史,使用方法如下

malloc_historyxx.memgraph[address]malloc_historyxx.memgraph--fullStacks[address]

1

2

3

工具的选择

以上讲了很多工具,当遇到内存问题时,那我们要如何进行选择呢?

这里有三种方法来考虑。您是否想查看对象的创建?您是否想查看内存中对象的引用或者地址内容?或者您是否想查看一个实例有多大?

可以根据上图所示,按照不同情况,来使用不同的工具。

图像

图片所占内存的大小与图片的尺寸有关,而不是图片的文件大小。 

举个例子,我们这里有一张590KB图片,而它的分辨率是2048px * 1536px。它实际使用的内存不是590KB,而是2048 * 1536 * 4 = 12 MB。

图片为什么会占这么多的内存?这还要从图片在iOS上显示的原理说起。一张图片文件从磁盘到展示需要经过三步:

加载

解压缩

渲染

更多关于图像以及如何优化图像的信息,请查看WWDC 2018 Image and Graphics Best Practices,也可以直接阅读前几天我们小伙伴发布的文章图像和图形的最佳实践)

图像渲染格式

sRGB格式

Wide格式

亮度和alpha 8格式

alpha 8格式

选择正确的图片格式

简单的回答是:不需要你来选择格式,而是应该让格式选择你。

用UIGraphicsImageRenderer代替UIGraphicsBeginImageContextWithOptions

使用UIGraphicsBeginImageContextWithOptions生成的图片,每个像素需要4个字节表示。建议使用UIGraphicsImageRenderer,这个方法是从iOS 10引入,在iOS 12上会自动选择最佳的图像格式,可以减少很多内存。UIGraphicsImageRenderer可以创建UIImage对象或者进行JPEG/PNG格式的编码。 

此外,如果想修改颜色,可以直接修改tintColor,不会有额外的内存开销。

下采样

当你缩小一幅图像的时候,会按照取平均值的办法把多个像素点变成一个像素点,这个过程称为下采样(Downsampling)。

UIImage在设置和调整大小的时候,需要将原始图像加压到内存中,然后对内部坐标空间做一系列转换,整个过程会消耗很多资源。我们可以使用ImageIO,它可以直接读取图像大小和元数据信息,不会带来额外的内存开销。 

这样处理,不但内存占用的更低了,而且执行速度也快了50%左右。

在后台时,对内存进行优化

假设在 App 里展示了一张很大图片,当我们切换到后台去做其它的操作时,这个图片还在占用内存。我们应该考虑在合适的时机去回收这类占用过大的数据。

监听UIApplicationWillEnterForeground和UIApplicationDidEnterBackground通知

viewWillAppear和viewDidDisappear方法

Demo演示

略过,基本上就是用上面说的命令去调试一个问题及优化方案去调试图片的内存问题

总结

内存是一个有限的共享资源,要学会使用Xcode分析内存工具,从而了解应用程序内存占用情况,并使用一些缩减应用程序内存占用空间的技巧和窍门。

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

推荐阅读更多精彩内容