馮·諾依曼體系:運算器 控制器 存儲器 輸入與輸出
內存即存儲器,用來存儲指令與數據
注:哈佛體系與普林斯頓體系的不同,使用兩個獨立的存儲器模塊,分別存儲指令和數據,每個存儲模塊都不允許指令和數據并存;使用獨立的兩條總線,分別作為CPU與每個存儲器之間的專用通信路徑,而這兩條總線之間毫無關聯。
物理地址:存儲器每一個字節單元給一個唯一的存儲器地址,即為物理地址,又叫實際地址或絕對地址。
虛擬存儲系統:操作系統層面做了一個物理地址與邏輯地址之間的映射。使用虛擬地址,作為讀寫的一部分。
1.程序可以使用一系列相鄰的虛擬地址來訪問物理內存中不相鄰的大內存緩沖區。
2.程序可以使用一系列虛擬地址來訪問大于可用物理內存的內存緩沖區。當物理內存的供應量變小時,內存管理器會將物理內存頁(通常大小為4KB)保存到磁盤文件。數據或代碼頁會根據需要在物理內存與磁盤之間移動。
3.不同進程使用的虛擬地址彼此隔離。一個進程中的代碼無法更改正在由另一進程或操作系統使用的物理內存。
解決兩個問題,一是多個程序同時運行,二是占用很大內存的程序。
一個程序在運行時,實際要用到的指令和數據都是很有限的,不可能從頭到尾同時用。那么對于一個程序來說,假裝自己有非常大的空間,實際上只要有條理的把暫時要用到的部分放進物理內存供CPU訪問就好,這樣第二個問題解決了。那既然每個程序(進程)只用一小塊,那整個物理內存就可以分給多個程序(進程)用了,第一個問題也迎刃而解。當然,這樣做的前提是,數據和指令的動態進出,用完了的暫時不用的踢出內存,需要用的及時加載進來。
內存泄漏:動態存儲分配的空間,在使用完畢后未釋放,結果導致一直占用該內存單元,直到程序結束。
內存溢出:沒有內存可用。
內存可細分五部分組成
代碼(指令):靜態,就是只讀的東西。
初始化數據:有初始值的變量,常量。
未初始化數據:只聲明未給值得變量。
棧:程序運行記錄,每個線程,也就是每個執行序列各有一個,都是編譯時候能確定好的,這里面的數據可以不使用指針也不會丟。
堆:最靈活,動態分配和釋放,編譯時不能確定,OC對象都存在堆里,通常都是用指針訪問 指針從線程棧中來,但不獨屬于某個線程,誰分配誰釋放說的是堆上對象的管理。
IOS內存管理
iOS的內存管理也是在以上的大框架下
最大的不同就是物理內存警告時
物理內存警告時,IOS會把能通過映射重新加載的內容直接清理出內存,對于不可再生的數據,iOS需要App進程配合處理,向各進程發送內存警告要求配合釋放內存,對于不能及時釋放足夠內存的,直接kill掉進程,必要時甚至是前臺運行的應用。
iOS內存管理機制的原理是引用計數,引用計數簡單來說就是統計一塊內存的所有權,當這塊內存被創建出來的時候,它的引用計數從0增加到1,表示有一個對象或指針持有這塊內存,擁有這塊內存的所有權,如果這時候有另外一個對象或指針指向這塊內存,那么為了表示這個后來的對象或指針對這塊內存的所有權,引用計數加1變為2,之后若有一個對象或指針不再指向這塊內存時,引用計數減1,表示這個對象或指針不再擁有這塊內存的所有權,當一塊內存的引用計數變為0,表示沒有任何對象或指針持有這塊內存,系統便會立刻釋放掉這塊內存。
其中在開發時引用計數又分為ARC(自動內存管理)和MRC(手動內存管理)。ARC的本質其實就是MRC,只不過是系統幫助開發者管理已創建的對象或內存空間,自動在系統認為合適的時間和地點釋放掉已經失去作用的內存空間,原理是一樣的。雖然ARC操作起來很方便,不但減少了代碼量,而且降低了內存出錯的概率,但因為ARC不一定會及時釋放,所以程序有時候可能會占用內存較大。而MRC若做得好,通過手動管理,及時釋放掉不需要的內存空間,便可保證程序長時間運行在良好狀態上。
在MRC中會引起引用計數變化的關鍵字有:alloc,retain,copy,release,autorelease。(strong關鍵字只用于ARC,作用等同于retain)
alloc:當一個類的對象創建,需要開辟內存空間的時候,會使用alloc,alloc是一個類方法,只能用類調用,它的作用是開辟一塊新的內存空間,并使這塊內存的引用計數從0增加到1,注意,是新的內存空間,每次用類alloc出來的都是一塊新的內存空間,與上一次alloc出來的內存空間沒有必然聯系,而且上一次alloc出來的內存空間仍然存在,不會被釋放。
retain:retain是一個實例方法,只能由對象調用,它的作用是使這個對象的內存空間的引用計數加1,并不會新開辟一塊內存空間,通常于賦值是調用,如:
對象2=[對象1 retain];表示對象2同樣擁有這塊內存的所有權。若只是簡單地賦值,如:對象2=對象1;那么當對象1的內存空間被釋放的時候,對象2便會成為野指針,再對對象2進行操作便會造成內存錯誤。
copy:copy同樣是一個實例方法,只能由對象調用,返回一個新的對象,它的作用是復制一個對象到一塊新的內存空間上,舊內存空間的引用計數不會變化,新的內存空間的引用計數從0增加到1,也就是說,雖然內容一樣,但實質上是兩塊內存,相當于克隆,一個變成兩個。其中copy又分為淺拷貝、深拷貝和真正的深拷貝,淺拷貝只是拷貝地址與retain等同;深拷貝是拷貝內容,會新開辟新內存,與retain不一樣;真正的深拷貝是對于容器類來說的,如數組類、字典類和集合類(包括可變和不可變),假設有一個數組類對象,普通的深拷貝會開辟一塊新內存存放這個對象,但這個數組對象里面的各個元素的地址卻沒有改變也就是說數組元素只是進行了retain或者淺拷貝而已,并沒有創建新的內存空間,而真正的深拷貝,不但數組對象本身進行了深拷貝,連數組元素都進行了深拷貝,即為各個數組元素開辟了新的內存空間。
release:release是一個實例方法,同樣只能由對象調用,它的作用是使對象的內存空間的引用計數減1,若引用計數變為0則系統會立刻釋放掉這塊內存。如果引用計數為0的基礎上再調用release,便會造成過度釋放,使內存崩潰;
autorelease:autorelease是一個實例方法,同樣只能由對象調用,它的作用于release類似,但不是立刻減1,相當于一個延遲的release,通常用于方法返回值的釋放,如便利構造器。autorelease會在程序走出自動釋放池時執行,通常系統會自動生成自動釋放池(即使是MRC下),也可以自己設定自動釋放池
引用計數(reference counting)
OC中每一個對象有一個關聯的整數retainCount用于記錄對象的使用情況,當retainCount為0時,對象被銷毀。
alloc copy new retain 等會使retainCount +1,
release會 -1
NSString 實際上是一個字符型常量,是沒有引用計數的
賦值操作是不會擁有對象的,引用計數不會加一,要持有對象需retain
引用計數不為0,因為引用計數為1的對象release時,系統對該對象回收,不做減一操作。
自動釋放池
不確定一個對象什么時候不再使用
autorelease 自動在未來釋放
原理:把對象添加到自動釋放池,當自動釋放池銷魂時,會對池中所有的對象發送release消息。
自動釋放池的創建
注:
1.自動釋放池只是給池中所有的對象發送release消息,當對象的引用計數>1時,對象無法銷毀。
2.autorelease不會改變對象的引用計數
ARC
ios5 引入ARC(Auto Reference Counting)
編譯時特性,不是運行時特性,更不是垃圾回收器
自動引用計數,在編譯時,自動加上retain release等,實現內存管理。
ARC開啟:可以在工程選項中選擇Targets -> Compile Phases -> Compile Sources,在里面找到對應文件,添加flag: -fobjc-arc
關閉:-fno-objc-arc? ;打開:-fobjc-arc
ARC IOS5開始引入,現在絕大部分開發都是ARC模式,以下應用主要是針對ARC模式下的應用。
屬性的內存管理
1.assign: 一般修飾基本數據類型
2.retain: release舊值,再retain新值
使用set方法,實質上會先保留新值,再釋放舊值,再設置新值,避免新舊值一樣時導致對象被釋放。
MRC寫法:
ARC寫法:
3.copy: release舊值,再copy新值(copy內容)
一般修飾 NSString、NSArray、NSDictionary等需要保護其封裝性的對象。尤其是在其內容可變的情況下 因此會拷貝一份內容給屬性使用,避免可能造成的對源內容進行改動
block一般使用copy修飾
4.weak ARC新引入 可代替assign,比assign多了一個特性(置nil)
delegate 一般用weak修飾,避免循環引用
set方法時,只設置新值
5.strong ARC新引入 可代替retain
block的內存管理
1.循環引用
block一般用copy修飾,當block又引用了對象的其他成員變量時,就會對這個變量本身產生強引用,那么變量本身和它自己的block就形成了循環引用。
__weak typeof (self) weakSelf = self;
ARC下要生成一個對自身的弱引用,表示block別再對self對象retain了,避免循環引用
2.block內部變量
1.block不能改變局部變量,要改變時需加__block修飾
2.block可改變全局變量
3.static變量也可在block中修改
內存問題的分析解決
1.僵尸對象和野指針
僵尸對象:內存被回收的對象
野指針:指向僵尸對象的指針,向野指針發送消息對導致崩潰EXC_BAD_ACCESS
1 在product-scheme-edit scheme-diagnostics中將enable zombie objects勾選上,下次再出現這樣的錯誤就可以準確定位了。
2 在Xcode-open developer tool-Instruments打開工具集,選擇Zombies工具可以對已安裝的應用進行僵尸對象檢測。
2.循環引用
1)在product-Analyze中使用靜態分析來檢測代碼中可能存在循環引用的問題。
2)在Xcode-open developer tool-Instruments打開工具集,選擇Leaks工具可以對已安裝的應用進行內存泄漏檢測,此工具能檢測靜態分析不會提示,但是到運行時才會出現的內存泄漏問題。
3.循環中對象占用內存大
循環內產生大量的臨時對象,直至循環結束才釋放,可能導致內存泄漏。
解決方法:在循環中創建自己的autoReleasePool,及時釋放占用內存大的臨時變量,減少內存占用峰值。
4.內存泄漏
通過Analyze來進行靜態代碼檢查,以發現在語法上顯而易見的內存泄露問題
內存泄露是運行時的問題,這時可用Instruments中的Allocation和Leaks來不斷重復操作App,發現和定位內存泄露點
5.過度釋放
原則上都會直接crash(然而由于某些特殊的情況,不會馬上crash)。
對于這種問題,可以直接使用Zombie,當過度釋放發生時會立即停在發生問題的位置,同時結合內存分配釋放歷史和調用棧,可以發現問題。
至于上文提到的程序不會crash的原因,其實有很多。
1.對象內存釋放時,所用內存并沒有完全被擦除,仍有舊對象部分數據可用
2.原內存位置被寫入同類或同樣結構的數據