如何定位內(nèi)存問題
今天主要講最常見的定位內(nèi)存問題,普遍使用ARC后摧找,開發(fā)者們從手動管理引用計數(shù)中解放出來相寇,但開啟了ARC并不是就不會存在內(nèi)存問題唤衫。
蘋果有句名言:ARC is only for NSObject。在iOS 中使用malloc分配的內(nèi)存佳励,ARC是不會處理的赃承,需要自己進行處理。(如CGPath等)
相關(guān)概念
1.內(nèi)存空間的劃分
一個進程占用的內(nèi)存空間拭嫁,包括5種數(shù)據(jù)區(qū):
(1)BSS段:通常存放未初始化的全局變量
(2)數(shù)據(jù)段:通常存放已初始化的全局變量
(3)代碼段:存放程序執(zhí)行代碼
(4)堆:存放進程運行中被動態(tài)分配的內(nèi)存段,如OC對象等
(5)棧:由編譯器自動分配釋放浇借,存放函數(shù)參數(shù)怕品,局部變量等
2.內(nèi)存溢出與內(nèi)存泄漏的概念
內(nèi)存溢出 out of memory,是指程序在申請內(nèi)存時闯估,沒有足夠的內(nèi)存空間供其使用吼和,出現(xiàn)out of memory
內(nèi)存泄露 memory leak,是指程序在申請內(nèi)存后尤辱,無法釋放已申請的內(nèi)存空間厢岂。在iOS中一般由循環(huán)引用塔粒、錯用Strong/copy等原因引起筐摘。
一、Analyze-靜態(tài)分析
檢測出的常見的三種泄露
(1).創(chuàng)建了對象沒有使用圃酵。
(2).創(chuàng)建了對象馍管,且初始化了,但初始化的值一直沒有讀取過捌锭。
Value store to ‘X’during its initialization is never.
(3).Potential leak of an object stored into 'XX'* 罗捎。 翻譯一下:XX對象的內(nèi)存單元有潛在的泄露風(fēng)險桨菜。
/**
* 創(chuàng)建了對象捉偏,但是并沒有使用泻红。
* Value Stored to 'XX' is never read
* 存儲在'XX'里的值從未被讀取過,
*/
- (void)leak1 {
NSString *str = [NSString string];
NSNumber *number;
number = @(str.length);
/*
最好的方法是將有關(guān)number的代碼都刪掉承桥,只對number賦值不使用凶异,那干嘛創(chuàng)建出來呢。
說我們沒有讀取過它剩彬,那就讀取一下喉恋,比如打開下面這句代碼,對它發(fā)送class消息糊肤,就不再會有這個提示了氓鄙。
這是一個比較常見和典型的錯誤,也很容易檢查出來
*/
// [number class];
}
/**
* 創(chuàng)建了一個(指針可變的)對象升酣,且初始化了态罪,但是初始化的值一直沒讀取過。
* Value Stored to 'str' during its initialization is never read
*/
- (void)leak2 {
NSString *str = [NSString string]; // 創(chuàng)建并初始化str绩聘,此時已經(jīng)有一個內(nèi)存單元保存str初始化的值
// NSString *str; // 這樣就內(nèi)存不泄露君纫,因為str是可變的芹彬,只需要先聲明就行。
// printf("str前 = %p\n",str);
str = @"ceshi"; // str被改變了会喝,指向了"ceshi"所在的地址,指針改變了枉阵,但之前保存初始化值的內(nèi)存空間還未釋放预茄,保存str初始化值的內(nèi)存單元泄露了。
// printf("str后 = %p\n",str); // 指針改變了
[str class];
// 再舉兩個例子拙徽,同理
NSArray *arr = [NSArray array];
// printf("arr前 = %p\n",arr);
// NSArray *arr; // 這樣就內(nèi)存不泄露
arr = @[@"1",@"2"];
// printf("arr后 = %p\n",arr); // 指針改變了
[arr class];
CGRect rect = self.view.frame;
// CGRect rect = CGRectZero; // 這樣就內(nèi)存不泄露
rect = CGRectMake(0, 0, 0, 0);
NSLog(@"rect = %@",NSStringFromCGRect(rect));
}
/**
* 調(diào)用了讓某個對象引用計數(shù)加1的函數(shù)诗宣,但沒有調(diào)用相應(yīng)讓其引用計數(shù)減1的函數(shù)召庞。
* Potential leak of an object stored into 'subImageRef'
* subImageRef對象的內(nèi)存單元有潛在的泄露風(fēng)險
*/
- (void)leak3 {
CGRect rect = CGRectMake(0, 0, 50, 50);
UIImage *image;
CGImageRef subImageRef = CGImageCreateWithImageInRect(image.CGImage, rect); // subImageRef 引用計數(shù) + 1;
UIImage* smallImage = [UIImage imageWithCGImage:subImageRef];
// 應(yīng)該調(diào)用對應(yīng)的函數(shù),讓subImageRef的引用計數(shù)減1,就不會泄露了
// CGImageRelease(subImageRef);
[smallImage class];
UIGraphicsEndImageContext();
例子二:
CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)_font.fontName, _font.pointSize, NULL);
[_string addAttributes:[NSDictionary dictionaryWithObjectsAndKeys:(__bridge NSObject*)fontRef, (NSString*)kCTFontAttributeName, nil]
range:NSMakeRange(0, [_string length])];
CGColorRef colorRef = _textColor.CGColor;
[_string addAttributes:[NSDictionary dictionaryWithObjectsAndKeys:(__bridge NSObject*)colorRef,(NSString*)kCTForegroundColorAttributeName, nil]
range:NSMakeRange(0, [_string length])];
// 應(yīng)該調(diào)用對應(yīng)的函數(shù)忘古,讓subImageRef的引用計數(shù)減1,就不會泄露了
// CFRelease(fontRef);
}
二存皂、Allocations
**Allocations是檢測程序運行過程中的內(nèi)存分配情況的逢艘。模板中一個叫(分配)Allocations骤菠,以及一個被稱為VM Tracker(虛擬機跟蹤)商乎。Allocations可以幫助我們查看全局內(nèi)存使用情況(Overall Memory Use): 從全局的角度監(jiān)測應(yīng)用程序的內(nèi)存使用情況,捕捉非預(yù)期的或大幅度的內(nèi)存增長鹉戚。
**
1.檢測內(nèi)存不合理引用
重復(fù)操作內(nèi)存是否持續(xù)增長抹凳,每次操作后,點擊mark generations button失都,會設(shè)置一個flag,然后查看每個迭代的詳細(xì)數(shù)據(jù)
2.選擇Detail的Allocation List咳焚,可以查看截取的某一時間段內(nèi)的內(nèi)存分配情況
3.選擇Call Tree 右側(cè)設(shè)置
Separate by Thread: 每個線程應(yīng)該分開考慮革半。只有這樣你才能揪出那些大量占用CPU的"重"線程
Invert Call Tree: 從上倒下跟蹤堆棧,這意味著你看到的表中的方法,將已從第0幀開始取樣,這通常你是想要的,只有這樣你才能看到CPU中話費時間最深的方法.也就是說FuncA{FunB{FunC}} 勾選此項后堆棧以C->B-A 把調(diào)用層級最深的C顯示在最外面
Hide System Libraries: 勾選此項你會顯示你app的代碼,這是非常有用的. 因為通常你只關(guān)心cpu花在自己代碼上的時間不是系統(tǒng)上的
Flatten Recursion: 遞歸函數(shù), 每個堆棧跟蹤一個條目
三流码、檢測內(nèi)存泄漏 Leaks
內(nèi)存泄漏使用Leaks檢測,如果對象發(fā)生內(nèi)存泄漏旅掂,detail panel 中會看到對象的retain release歷史記錄,如果非對象發(fā)生內(nèi)存泄漏觉阅,就會看到malloc和free的調(diào)用歷史秘车。
1.選中Leaks Checks,在Details所在欄中選擇CallTree
2.Call Tree會給我們大概的位置叮趴,有時候會給我們精確的位置,選中出現(xiàn)內(nèi)存泄漏的區(qū)域,縮小范圍眯亦,篩選數(shù)據(jù)妻率。
3.且在右下 Display Settings 中勾選 Invert Call Tree 和 Hide System Libraries 或其他選項可以過濾顯示的數(shù)據(jù)。
4.在導(dǎo)航欄的篩選框中走净,我們可以輸入關(guān)鍵字來篩選數(shù)據(jù)伏伯。
四捌袜、 查找野指針 Zombies
在開啟ARC后琢蛤,可以很大程度上避免產(chǎn)生EXC_BAD_ACCESS錯誤抛虏,但也是有出現(xiàn)可能的套才,比如非NSObject對象的產(chǎn)生的野指針。
1.使用Zombies工具沸毁,啟動Zombies后在內(nèi)部設(shè)置了NSZombieEnabled為True傻寂。
啟用了NSZombieEnabled的話,它會用一個僵尸來替換默認(rèn)的dealloc實現(xiàn)搂誉,也就是在引用計數(shù)降到0時静檬,該僵尸實現(xiàn)會將該對象轉(zhuǎn)換成僵尸對象。僵尸對象的作用是在你向它發(fā)送消息時侮腹,就不會向之前那樣Crash或者產(chǎn)生 一個難以理解的行為稻励,而是放出一個錯誤消息望抽,它會顯示一段日志并自動跳入調(diào)試器, 因此我們就可以找到具體或者大概是哪個對象被錯誤的釋放了煤篙。
基本上通過查看Zombies工具給出的信息找出錯誤代碼行是比較簡單的舰蟆,Zombies也只有在產(chǎn)生EXC_BAD_ACCESS錯誤時才有用狸棍。
2.XCode也提供了手動設(shè)置NSZombieEnabled環(huán)境變量的方法草戈,不過設(shè)置NSZombieEnabled為True后,會導(dǎo)致內(nèi)存占用的增長丙猬,同時會影響Leaks工具的調(diào)試,這是因為設(shè)置NSZombieEnabled會用僵尸對象來代替已釋放對象庭瑰。
相關(guān)文檔:[iOS開發(fā)之性能調(diào)試Instruments(一)](http://www.reibang.com/p/8dfc477e9d70e/)