Instruments 之 定位內(nèi)存問題(二)

如何定位內(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)險桨菜。


Analyze.png
/**
 * 創(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ù)

Allocations.png

2.選擇Detail的Allocation List咳焚,可以查看截取的某一時間段內(nèi)的內(nèi)存分配情況

3.選擇Call Tree 右側(cè)設(shè)置


settings

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


leak1

2.Call Tree會給我們大概的位置叮趴,有時候會給我們精確的位置,選中出現(xiàn)內(nèi)存泄漏的區(qū)域,縮小范圍眯亦,篩選數(shù)據(jù)妻率。

leak2

3.且在右下 Display Settings 中勾選 Invert Call Tree 和 Hide System Libraries 或其他選項可以過濾顯示的數(shù)據(jù)。


leak3

4.在導(dǎo)航欄的篩選框中走净,我們可以輸入關(guān)鍵字來篩選數(shù)據(jù)伏伯。


leak4

四捌袜、 查找野指針 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錯誤時才有用狸棍。


zombies

2.XCode也提供了手動設(shè)置NSZombieEnabled環(huán)境變量的方法草戈,不過設(shè)置NSZombieEnabled為True后,會導(dǎo)致內(nèi)存占用的增長丙猬,同時會影響Leaks工具的調(diào)試,這是因為設(shè)置NSZombieEnabled會用僵尸對象來代替已釋放對象庭瑰。


Zombies

相關(guān)文檔:[iOS開發(fā)之性能調(diào)試Instruments(一)](http://www.reibang.com/p/8dfc477e9d70e/)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弹灭,一起剝皮案震驚了整個濱河市揪垄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捡鱼,老刑警劉巖酷愧,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伟墙,死亡現(xiàn)場離奇詭異,居然都是意外死亡就乓,警方通過查閱死者的電腦和手機拱烁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門戏自,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人志衣,你說我怎么就攤上這事猛们。” “怎么了绿店?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵假勿,是天一觀的道長。 經(jīng)常有香客問我恶导,道長堡距,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任缤沦,我火速辦了婚禮易稠,結(jié)果婚禮上驶社,老公的妹妹穿的比我還像新娘。我一直安慰自己届巩,他們只是感情好份乒,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布或辖。 她就那樣靜靜地躺著,像睡著了一般颂暇。 火紅的嫁衣襯著肌膚如雪耳鸯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天煌集,我揣著相機與錄音捌省,去河邊找鬼碉钠。 笑死,一個胖子當(dāng)著我的面吹牛祝高,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乍赫,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼雷厂,長吁一口氣:“原來是場噩夢啊……” “哼叠殷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起像棘,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤壶冒,失蹤者是張志新(化名)和其女友劉穎缕题,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胖腾,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡烟零,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了胸嘁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓶摆。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖性宏,靈堂內(nèi)的尸體忽然破棺而出群井,到底是詐尸還是另有隱情,我是刑警寧澤毫胜,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站酵使,受9級特大地震影響荐吉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜口渔,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一样屠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦痪欲、人聲如沸悦穿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栗柒。三九已至,卻和暖如春知举,著一層夾襖步出監(jiān)牢的瞬間瞬沦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工雇锡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逛钻,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓遮糖,卻偏偏與公主長得像绣的,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子欲账,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容