让人懵逼的 iOS 系统内存分配问题
最近应用中出现低内存被杀的情况,所以就想办法对造成这一个问题的根源进行定位,主要是两个比较 Low 的思路
- 线下使用
Instruments
、MLeaksFinder 来进行内存泄露的定位,后期可能考虑把测试阶段的内存泄漏对象和泄漏位置上报到测试服务器进行数据分析,进而更精确定位问题 - 线上继续统计 Abort 率,然后对于出现内存警告、Abort 的情况,及时上报 App 的内存走势,这个走势包括 App 使用的物理内存大小,以及当前设备的整体内存使用情况。当然不会持续收集内存,我们会在 ViewController 初始化和销毁的时候进行收集,这样基本可以把问题定位到 Controller 级别。
让人懵逼的地方就是如何获取 app 的内存和设备的整体内存情况,在网上查了一下,答案千奇百怪,而且统计的结果差异也比较大,所以才有了此文
系统内存分配
据查阅 Apple 的官方文档,操作系统的内存主要分为 Used Memory
、Free Memory
,Used Memory
又可以分为Wired Memory
、Active Memory
、Inactive Memory
,同时提到了一个Purgeable Memory
,暂且把它归类为 Active Memory
吧。
-
Free Memory:未使用的 RAM 容量,随时可以被应用分配使用
-
Wired Memory:用来存放内核代码和数据结构,它主要为内核服务,如负责网络、文件系统之类的;对于应用、framework、一些用户级别的软件是没办法分配此内存的。但是应用程序也会对 Wired Memory 的分配有所影响。
-
Active Memory:活跃的内存,正在被使用或很短时间内被使用过
-
Inactive Memory:最近被使用过,但是目前处于不活跃状态
例如,如果您使用了邮件然后退出,则邮件曾经使用的 RAM 会标记为“不活跃”内存。“不活跃”内存可供其他应用软件使用,就像“可用”内存一样。但是,如果在其他应用软件占用邮件的“不活跃”内存之前打开了邮件,邮件的打开速度会更快,因为其“不活跃”内存会转换为“活跃”内存,而不是从较慢的驱动器进行载入。
-
Purgeable Memory:这个是查阅资料发现的,同时第三方库中有统计此内存的大小,所以记录一下。可以理解为可释放的内存,主要是大对象或大内存块才可以使用的内存,此内存会在内存紧张的时候自动释放掉,一会可以查看 Demo 来验证这一事实。
应用物理内存
这个就是理解上的物理内存,对于 app 的内存使用应该检测这一数值的变化,而检测虚拟内存的话意义不大,对于物理内存和虚拟内存在 iOS 上的分配可以查看这篇文章:先弄清楚这里的学问,再来谈 iOS 内存管理与优化(一)。对于物理内存的用量检测方法比较常见:(更新正确的获取App内存占用的方法:http://ddrccw.github.io/2017/12/30/reverse-xcode-with-lldb-and-hopper-disassembler 、http://www.samirchen.com/ios-app-memory-usage/)
// 获得当前 App 的内存占用情况
- (NSUInteger)getResidentMemory {
struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
int r = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
if (r == KERN_SUCCESS) {
NSLog(@"resident_size %lu",t_info.resident_size / 1024/1024);
return t_info.resident_size;
}
else {
return -1;
}
}
测试
我主要是在 app 中放了一个按钮,每点击一次分配一百兆物理内存,然后查看 app 的内存使用情况以及系统的内存分配情况,对于系统的内存分配查看方式,可以参考这里:iOS-System-Services/System Services/Utilities/SSMemoryInfo.m
在 app 低内存崩溃前,一直收集点击按钮后的内存变化,如下
2017-03-09 21:28:03.112138 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:03.112280 LDAPM[491:46357] free 549.734375
2017-03-09 21:28:03.112338 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:03.112361 LDAPM[491:46357] used 1226.390625
2017-03-09 21:28:03.112381 LDAPM[491:46357] active 740.156250
2017-03-09 21:28:03.112399 LDAPM[491:46357] inactive 291.109375
2017-03-09 21:28:03.112417 LDAPM[491:46357] wired 195.125000
2017-03-09 21:28:03.112544 LDAPM[491:46357] purgableMemory 11.515625
2017-03-09 21:28:03.112613 LDAPM[491:46357] resident_size 27
2017-03-09 21:28:36.326194 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:36.326284 LDAPM[491:46357] free 448.093750
2017-03-09 21:28:36.326307 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:36.326322 LDAPM[491:46357] used 1330.734375
2017-03-09 21:28:36.326340 LDAPM[491:46357] active 846.015625
2017-03-09 21:28:36.326358 LDAPM[491:46357] inactive 289.593750
2017-03-09 21:28:36.326377 LDAPM[491:46357] wired 195.125000
2017-03-09 21:28:36.326395 LDAPM[491:46357] purgableMemory 12.531250
2017-03-09 21:28:36.326450 LDAPM[491:46357] resident_size 134
2017-03-09 21:28:42.708484 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:42.708571 LDAPM[491:46357] free 352.718750
2017-03-09 21:28:42.708593 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:42.708607 LDAPM[491:46357] used 1427.968750
2017-03-09 21:28:42.708626 LDAPM[491:46357] active 944.703125
2017-03-09 21:28:42.708656 LDAPM[491:46357] inactive 289.203125
2017-03-09 21:28:42.708677 LDAPM[491:46357] wired 194.062500
2017-03-09 21:28:42.708695 LDAPM[491:46357] purgableMemory 12.531250
2017-03-09 21:28:42.708715 LDAPM[491:46357] resident_size 234
2017-03-09 21:28:46.940049 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:46.940146 LDAPM[491:46357] free 252.437500
2017-03-09 21:28:46.940166 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:46.940185 LDAPM[491:46357] used 1527.406250
2017-03-09 21:28:46.940203 LDAPM[491:46357] active 1043.875000
2017-03-09 21:28:46.940221 LDAPM[491:46357] inactive 289.484375
2017-03-09 21:28:46.940239 LDAPM[491:46357] wired 194.046875
2017-03-09 21:28:46.940256 LDAPM[491:46357] purgableMemory 12.531250
2017-03-09 21:28:46.940275 LDAPM[491:46357] resident_size 334
2017-03-09 21:28:49.930067 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:49.930154 LDAPM[491:46357] free 152.187500
2017-03-09 21:28:49.930177 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:49.930221 LDAPM[491:46357] used 1627.515625
2017-03-09 21:28:49.930248 LDAPM[491:46357] active 1143.921875
2017-03-09 21:28:49.930267 LDAPM[491:46357] inactive 289.515625
2017-03-09 21:28:49.930285 LDAPM[491:46357] wired 194.078125
2017-03-09 21:28:49.930303 LDAPM[491:46357] purgableMemory 12.593750
2017-03-09 21:28:49.930322 LDAPM[491:46357] resident_size 434
2017-03-09 21:28:52.780752 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:52.780830 LDAPM[491:46357] free 82.390625
2017-03-09 21:28:52.780852 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:52.780867 LDAPM[491:46357] used 1748.640625
2017-03-09 21:28:52.780918 LDAPM[491:46357] active 1078.281250
2017-03-09 21:28:52.780943 LDAPM[491:46357] inactive 479.546875
2017-03-09 21:28:52.780961 LDAPM[491:46357] wired 190.812500
2017-03-09 21:28:52.780979 LDAPM[491:46357] purgableMemory 12.562500
2017-03-09 21:28:52.780999 LDAPM[491:46357] resident_size 534
2017-03-09 21:28:55.454389 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:55.454456 LDAPM[491:46357] free 21.062500
2017-03-09 21:28:55.454476 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:55.454493 LDAPM[491:46357] used 1801.875000
2017-03-09 21:28:55.454511 LDAPM[491:46357] active 1070.968750
2017-03-09 21:28:55.454529 LDAPM[491:46357] inactive 540.312500
2017-03-09 21:28:55.454546 LDAPM[491:46357] wired 190.593750
2017-03-09 21:28:55.454564 LDAPM[491:46357] purgableMemory 10.906250
2017-03-09 21:28:55.454587 LDAPM[491:46357] resident_size 634
2017-03-09 21:28:58.757493 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:58.757688 LDAPM[491:46357] free 25.484375
2017-03-09 21:28:58.757710 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:28:58.757804 LDAPM[491:46357] used 1119.265625
2017-03-09 21:28:58.757830 LDAPM[491:46357] active 624.609375
2017-03-09 21:28:58.757848 LDAPM[491:46357] inactive 303.906250
2017-03-09 21:28:58.757866 LDAPM[491:46357] wired 190.796875
2017-03-09 21:28:58.757889 LDAPM[491:46357] resident_size 59
2017-03-09 21:29:00.488180 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:00.488253 LDAPM[491:46357] free 38.281250
2017-03-09 21:29:00.488273 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:00.488289 LDAPM[491:46357] used 1020.781250
2017-03-09 21:29:00.488308 LDAPM[491:46357] active 563.328125
2017-03-09 21:29:00.488326 LDAPM[491:46357] inactive 267.046875
2017-03-09 21:29:00.488343 LDAPM[491:46357] wired 190.406250
2017-03-09 21:29:00.488382 LDAPM[491:46357] purgableMemory 0.062500
2017-03-09 21:29:00.488416 LDAPM[491:46357] resident_size 126
2017-03-09 21:29:02.915796 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:02.915882 LDAPM[491:46357] free 46.578125
2017-03-09 21:29:02.915902 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:02.915917 LDAPM[491:46357] used 844.250000
2017-03-09 21:29:02.915935 LDAPM[491:46357] active 448.140625
2017-03-09 21:29:02.915952 LDAPM[491:46357] inactive 204.609375
2017-03-09 21:29:02.915970 LDAPM[491:46357] wired 191.500000
2017-03-09 21:29:02.915993 LDAPM[491:46357] resident_size 63
2017-03-09 21:29:05.359697 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:05.359786 LDAPM[491:46357] free 39.750000
2017-03-09 21:29:05.359810 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:05.359858 LDAPM[491:46357] used 730.562500
2017-03-09 21:29:05.359885 LDAPM[491:46357] active 363.437500
2017-03-09 21:29:05.359904 LDAPM[491:46357] inactive 176.109375
2017-03-09 21:29:05.359922 LDAPM[491:46357] wired 191.015625
2017-03-09 21:29:05.359979 LDAPM[491:46357] resident_size 53
2017-03-09 21:29:08.239699 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:08.239776 LDAPM[491:46357] free 36.656250
2017-03-09 21:29:08.239797 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:08.239813 LDAPM[491:46357] used 621.703125
2017-03-09 21:29:08.239831 LDAPM[491:46357] active 288.640625
2017-03-09 21:29:08.239848 LDAPM[491:46357] inactive 142.781250
2017-03-09 21:29:08.239865 LDAPM[491:46357] wired 190.281250
2017-03-09 21:29:08.240040 LDAPM[491:46357] resident_size 39
2017-03-09 21:29:11.167796 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:11.167871 LDAPM[491:46357] free 34.109375
2017-03-09 21:29:11.167893 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:11.168042 LDAPM[491:46357] used 638.437500
2017-03-09 21:29:11.168088 LDAPM[491:46357] active 311.562500
2017-03-09 21:29:11.168244 LDAPM[491:46357] inactive 149.296875
2017-03-09 21:29:11.168373 LDAPM[491:46357] wired 177.625000
2017-03-09 21:29:11.168492 LDAPM[491:46357] resident_size 85
2017-03-09 21:29:13.845295 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:13.845364 LDAPM[491:46357] free 41.937500
2017-03-09 21:29:13.845385 LDAPM[491:46357] TotalMemory 2048.000000
2017-03-09 21:29:13.845401 LDAPM[491:46357] used 637.250000
2017-03-09 21:29:13.845419 LDAPM[491:46357] active 321.453125
2017-03-09 21:29:13.845440 LDAPM[491:46357] inactive 145.171875
2017-03-09 21:29:13.845458 LDAPM[491:46357] wired 170.640625
2017-03-09 21:29:13.845522 LDAPM[491:46357] resident_size 152
2017-03-09 21:29:13.855866 LDAPM[491:46357] applicationDidReceiveMemoryWarning
Message from debugger: Terminated due to memory issue
可以发现在我们的 app 内存使用正常的情况下,free
内存、 active
内存以及 app 当前的物理内存变化都是很正常的,以 100 为单位变化。但是后期 app 要求分配过多内存的时候,发现 active
内存不再以 100 为额度增长,这个时候应该是操作系统基于 jetsam
机制开始杀死一些后台优先级低的 app 了。再进一步看,发现内存紧张的时候,purgeableMemory
不再打印了,是因为它的值变为了 0,所以我没有继续打印,也符合刚才的描述,在内存紧张时,可以自动释放的内存,这一手段也可以用来降低内存峰值。
Q & A
1.为什么 app 崩溃的时候,我的应用使用内存不多,但是系统剩余内存很少的情况下,首先杀掉了我的应用呢?
这个问题,我以前也比较好奇,觉得这个不符合常理。但是通过刚才的测试来看,在自身物理内存不断申请的情况下,当物理内存过大的时候,通过以上代码收集到的值并不是很大,反而变小了。可能是代码的问题,也可能是操作系统的问题。但是你所使用的代码或者第三方平台的收集策略很可能与此类似。收集到一个很小的值,但是并不代表你的 app 的内存使用情况很正确,很可能存在内存暴涨,但是收集结果却没有显示出来。
2. 为什么上面的数据 free memory + used memory (active + inactive + purgeableMemory) 的值不等于 TotalMemory 呢?
这个问题我也很好奇,而且会发现当你自身 app 内存申请不合理的时候 free memory + used memory 的值也会和正常情况下不同,目前只能猜测系统用这块内存来做了什么神秘的事情,如有对操作系统熟悉的同学,望告知!!