IOS內(nèi)存管理筆記

基本概念

  • 馮·諾依曼結(jié)構(gòu):運(yùn)算器 控制器 存儲器 輸入與輸出

  • 內(nèi)存即存儲器,用來存儲指令與數(shù)據(jù)

注:哈佛結(jié)構(gòu)與普林斯頓結(jié)構(gòu)的不同,使用兩個獨立的存儲器模塊,分別存儲指令和數(shù)據(jù),每個存儲模塊都不允許指令和數(shù)據(jù)并存;使用獨立的兩條總線,分別作為CPU與每個存儲器之間的專用通信路徑,而這兩條總線之間毫無關(guān)聯(lián)。

  • 物理地址:存儲器每一個字節(jié)單元給一個唯一的存儲器地址,即為物理地址,又叫實際地址或絕對地址。
  • 虛擬存儲系統(tǒng):操作系統(tǒng)層面做了一個物理地址與邏輯地址之間的映射。使用虛擬地址,作為讀寫的一部分。優(yōu)勢如下:

1)程序可以使用一系列相鄰的虛擬地址來訪問物理內(nèi)存中不相鄰的大內(nèi)存緩沖區(qū)。

2)程序可以使用一系列虛擬地址來訪問大于可用物理內(nèi)存的內(nèi)存緩沖區(qū)。當(dāng)物理內(nèi)存的供應(yīng)量變小時,內(nèi)存管理器會將物理內(nèi)存頁(通常大小為4KB)保存到磁盤文件。數(shù)據(jù)或代碼頁會根據(jù)需要在物理內(nèi)存與磁盤之間移動。

3)不同進(jìn)程使用的虛擬地址彼此隔離。一個進(jìn)程中的代碼無法更改正在由另一進(jìn)程或操作系統(tǒng)使用的物理內(nèi)存。

解決兩個問題,一是多個程序同時運(yùn)行,二是占用很大內(nèi)存的程序。

一個程序在運(yùn)行時,實際要用到的指令和數(shù)據(jù)都是很有限的,不可能從頭到尾同時用。那么對于一個程序來說,假裝自己有非常大的空間,實際上只要有條理的把暫時要用到的部分放進(jìn)物理內(nèi)存供CPU訪問就好,這樣第二個問題解決了。那既然每個程序(進(jìn)程)只用一小塊,那整個物理內(nèi)存就可以分給多個程序(進(jìn)程)用了,第一個問題也迎刃而解。當(dāng)然,這樣做的前提是,數(shù)據(jù)和指令的動態(tài)進(jìn)出,用完了的暫時不用的踢出內(nèi)存,需要用的及時加載進(jìn)來。

  • 內(nèi)存泄漏:動態(tài)存儲分配的空間,在使用完畢后未釋放,結(jié)果導(dǎo)致一直占用該內(nèi)存單元,直到程序結(jié)束。
  • 內(nèi)存溢出:沒有內(nèi)存可用。

內(nèi)存可細(xì)分五部分組成

  • 代碼(指令):靜態(tài),就是只讀的東西。
  • 初始化數(shù)據(jù):有初始值的變量,常量。
  • 未初始化數(shù)據(jù):只聲明未給值得變量。
  • 棧:程序運(yùn)行記錄,每個線程,也就是每個執(zhí)行序列各有一個,都是編譯時候能確定好的,這里面的數(shù)據(jù)可以不使用指針也不會丟。
  • 堆:最靈活,動態(tài)分配和釋放,編譯時不能確定,OC對象都存在堆里,通常都是用指針訪問 指針從線程棧中來,但不獨屬于某個線程,誰分配誰釋放說的是堆上對象的管理。

IOS內(nèi)存管理

iOS的內(nèi)存管理也是在以上的大框架下

最大的不同就是物理內(nèi)存吃緊時的處理

當(dāng)物理內(nèi)存吃緊時,IOS會把能通過映射重新加載的內(nèi)容直接清理出內(nèi)存,對于不可再生的數(shù)據(jù),iOS10需要App進(jìn)程配合處理,向各進(jìn)程發(fā)送內(nèi)存警告要求配合釋放內(nèi)存,對于不能及時釋放足夠內(nèi)存的,直接kill掉進(jìn)程,必要時甚至是前臺運(yùn)行的App。

引用計數(shù)(reference counting)

OC中每一個對象有一個關(guān)聯(lián)的整數(shù)retainCount用于記錄對象的使用情況,當(dāng)retainCount為0時,對象被銷毀。

alloc copy new retain 等會使retainCount +1,

release會 -1

NSString 實際上是一個字符型常量,是沒有引用計數(shù)的

賦值操作是不會擁有對象的,引用計數(shù)不會加一,要持有對象需retain

引用計數(shù)不為0,因為引用計數(shù)為1的對象release時,系統(tǒng)對該對象回收,不做減一操作

基本原則:

自動釋放池

不確定一個對象什么時候不再使用

autorelease 自動在未來釋放

原理:把對象添加到自動釋放池,當(dāng)自動釋放池銷魂時,會對池中所有的對象發(fā)送release消息。

自動釋放池的創(chuàng)建

  1. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
    [pool release];

  2. @autoreleasepool{

      //這里寫代碼
    

    }

注:
1.自動釋放池只是給池中所有的對象發(fā)送release消息,當(dāng)對象的引用計數(shù)>1時,對象無法銷毀。
2.autorelease不會改變對象的引用計數(shù)

ARC

ios5 引入ARC(Auto Reference Counting)

編譯時特性,不是運(yùn)行時特性,更不是垃圾回收器

自動引用計數(shù),在編譯時,自動加上retain release等,實現(xiàn)內(nèi)存管理。

ARC開啟:可以在工程選項中選擇Targets -> Compile Phases -> Compile Sources,在里面找到對應(yīng)文件,添加flag: -fobjc-arc

關(guān)閉:-fno-objc-arc

實際中的使用

ARC IOS5開始引入,現(xiàn)在絕大部分開發(fā)都是ARC模式,以下應(yīng)用主要是針對ARC模式下的應(yīng)用。

屬性的內(nèi)存管理

1.assign: 一般修飾基本數(shù)據(jù)類型

2.retain: release舊值,再retain新值

使用set方法,實質(zhì)上會先保留新值,再釋放舊值,再設(shè)置新值,避免新舊值一樣時導(dǎo)致對象被釋放。

MRC寫法:

- (void)setCount:(NSObject *)count {
     [count retain];
     [_count release];
      _count = count;
 }

ARC寫法:

 - (void)setCount:(NSObject *)count {
      _count = count;
 }

3.copy: release舊值,再copy新值(copy內(nèi)容)

一般修飾 NSString、NSArray、NSDictionary等需要保護(hù)其封裝性的對象。尤其是在其內(nèi)容可變的情況下 因此會拷貝一份內(nèi)容給屬性使用,避免可能造成的對源內(nèi)容進(jìn)行改動

block一般使用copy修飾

4.weak ARC新引入 可代替assign,比assign多了一個特性(置nil)

delegate 一般用weak修飾,避免循環(huán)引用

set方法時,只設(shè)置新值

5.strong ARC新引入 可代替retain

block的內(nèi)存管理

1.循環(huán)引用

block一般用copy修飾,當(dāng)block又引用了對象的其他成員變量時,就會對這個變量本身產(chǎn)生強(qiáng)引用,那么變量本身和它自己的block就形成了循環(huán)引用。

__weak typeof(self) weakSelf = self;

ARC下要生成一個對自身的弱引用,表示block別再對self對象retain了,避免循環(huán)引用

2.block內(nèi)部變量

1.block不能改變局部變量,要改變時需加__block修飾

2.block可改變?nèi)肿兞?/p>

3.static變量也可在block中修改

內(nèi)存問題的分析解決

1.僵尸對象和野指針

僵尸對象:內(nèi)存被回收的對象

野指針:指向僵尸對象的指針,向野指針發(fā)送消息對導(dǎo)致崩潰EXC_BAD_ACCESS

1)在product-scheme-edit scheme-diagnostics中將enable zombie objects勾選上,下次再出現(xiàn)這樣的錯誤就可以準(zhǔn)確定位了。

2)在Xcode-open developer tool-Instruments打開工具集,選擇Zombies工具可以對已安裝的應(yīng)用進(jìn)行僵尸對象檢測。

2.循環(huán)引用

1)在product-Analyze中使用靜態(tài)分析來檢測代碼中可能存在循環(huán)引用的問題。

2)在Xcode-open developer tool-Instruments打開工具集,選擇Leaks工具可以對已安裝的應(yīng)用進(jìn)行內(nèi)存泄漏檢測,此工具能檢測靜態(tài)分析不會提示,但是到運(yùn)行時才會出現(xiàn)的內(nèi)存泄漏問題。

3.循環(huán)中對象占用內(nèi)存大

循環(huán)內(nèi)產(chǎn)生大量的臨時對象,直至循環(huán)結(jié)束才釋放,可能導(dǎo)致內(nèi)存泄漏。

解決方法:在循環(huán)中創(chuàng)建自己的autoReleasePool,及時釋放占用內(nèi)存大的臨時變量,減少內(nèi)存占用峰值。

例:

 for (int i = 0; i < 10000; i ++) {
      @autoreleasepool {
         Person * soldier = [[Person alloc]init];
         [soldier fight];
      }
 }

4.內(nèi)存泄漏

通過Analyze來進(jìn)行靜態(tài)代碼檢查,以發(fā)現(xiàn)在語法上顯而易見的內(nèi)存泄露問題

內(nèi)存泄露是運(yùn)行時的問題,這時可用Instruments中的Allocation和Leaks來不斷重復(fù)操作App,發(fā)現(xiàn)和定位內(nèi)存泄露點

5.過度釋放

原則上都會直接crash(然而由于某些特殊的情況,不會馬上crash)。

對于這種問題,可以直接使用Zombie,當(dāng)過度釋放發(fā)生時會立即停在發(fā)生問題的位置,同時結(jié)合內(nèi)存分配釋放歷史和調(diào)用棧,可以發(fā)現(xiàn)問題。

至于上文提到的不會crash的原因,其實有很多,比如:

1)對象內(nèi)存釋放時,所用內(nèi)存并沒有完全被擦除,仍有舊對象部分?jǐn)?shù)據(jù)可用

2)原內(nèi)存位置被寫入同類或同樣結(jié)構(gòu)的數(shù)據(jù)

菜鳥一枚,有錯誤之處還望大家批評指正!露露

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 內(nèi)存管理 簡述OC中內(nèi)存管理機(jī)制。與retain配對使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 1,989評論 1 16
  • 內(nèi)存管理 ARC處理原理 ARC是Objective-C編譯器的特性,而不是運(yùn)行時特性或者垃圾回收機(jī)制,ARC所做...
    b485c88ab697閱讀 11,236評論 3 47
  • 為什么進(jìn)行內(nèi)存管理? 由于移動設(shè)備的內(nèi)存極其有限,所以每個APP所占的內(nèi)存也是有限制的,當(dāng)app所占用的內(nèi)存較多時...
    天天想念閱讀 905評論 1 7
  • 馮·諾依曼體系:運(yùn)算器 控制器 存儲器 輸入與輸出 內(nèi)存即存儲器,用來存儲指令與數(shù)據(jù) 注:哈佛體系與普林斯頓體系的...
    小李龍彪閱讀 667評論 0 8
  • iOS內(nèi)存管理 概述 什么是內(nèi)存管理 應(yīng)用程序內(nèi)存管理是在程序運(yùn)行時分配內(nèi)存(比如創(chuàng)建一個對象,會增加內(nèi)存占用)與...
    蚊香醬閱讀 5,749評論 8 119