前言
本文主要記錄在iOS開發(fā)中發(fā)現(xiàn)的一個系統(tǒng)級別內(nèi)存泄露的過程妆丘。測試iOS系統(tǒng)11.2.1寺擂,設備iPhoneX。
如何復現(xiàn)
下面是復現(xiàn)泄漏的測試代碼侧到,LeakObject
是一個沒有任何多余代碼的類伟叛,繼承自NSObject
私痹。
LeakObject *leakObject = [LeakObject new];
for(int i = 0; i < 10000 * 1000; ++i) {
id value = @"hello";
[leakObject validateValue:&value forKey:@"notexistkey" error:nil];
}
當對一個沒有實現(xiàn)校驗方法的key進行validateValue
時,就會有少量內(nèi)存泄漏统刮。如果執(zhí)行很多次紊遵,結(jié)果還是很可觀的。上面的代碼會讓內(nèi)存飆到160M侥蒙。
定位泄漏源
這個泄漏使用Instruments的Leaks模版可以很快的發(fā)現(xiàn)暗膜,但是代碼卻不好定位。下面是Leaks報告的泄漏截圖鞭衩。
可以看出学搜,泄漏發(fā)生在validateValue:forKey:error:
,再細看右邊的調(diào)用棧论衍,可以看到這塊內(nèi)存是由malloc
分配的瑞佩。所以很有可能是這個系統(tǒng)方法內(nèi)部發(fā)生了泄漏。
使用符號斷點深入觀察系統(tǒng)方法
首先使用符號斷點坯台,讓程序在validateValue:forKey:error:
處停下炬丸。
運行程序,命中斷點后蜒蕾,我們就可以觀察
validateValue:forKey:error:
的匯編代碼了稠炬。尋找Leak的內(nèi)存來源
在匯編代碼中焕阿,我發(fā)現(xiàn)了一個malloc調(diào)用和一個free調(diào)用。
0x1048272ef <+63>: callq 0x10498b1da ; symbol stub for: malloc
...
0x104827389 <+217>: callq 0x10498b066 ; symbol stub for: free
通過單步調(diào)試發(fā)現(xiàn)首启,malloc出來的內(nèi)存主要用來存儲key暮屡,并且把首字母變成大寫,應該是為了方便構(gòu)成validate<Key>:error:
的selector name毅桃。不過如果對象上沒有校驗這個key的方法褒纲,那么代碼會直接jump到free調(diào)用的下二行。這樣這個內(nèi)存塊就永遠不會被釋放了疾嗅。
0x104827390 <+224>: movb $0x1, %r14b
當我們給LeakObject加上notexistkey的校驗方法后外厂,單步可以發(fā)現(xiàn)free被調(diào)用冕象。下面是增加的校驗方法代承。
- (BOOL)validateNotexistkey:(id *)value error:(NSError **)error {
return YES;
}
總結(jié)
這次泄漏的尋找過程,大致可以分為
- 使用Instruments Leak模版初步定位
- 使用符號斷點深入泄漏方法渐扮,如果泄漏的方法不是系統(tǒng)或者第三方靜態(tài)(動態(tài))庫的方法论悴,就不用這么麻煩了。
- 關(guān)注泄漏內(nèi)存塊的分配釋放方式墓律,在源碼或者匯編代碼中尋找匹配的內(nèi)存塊膀估。
由于這次泄漏的僅僅是malloc內(nèi)存塊,所以OC的引用計數(shù)記錄并不能起什么作用耻讽。