最近應用中出現低內存被殺的情況,所以就想辦法對造成這一個問題的根源進行定位,主要是兩個比較 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 的值也會和正常情況下不同,目前只能猜測系統用這塊內存來做了什么神秘的事情,如有對操作系統熟悉的同學,望告知!!