一個程序內存結構能夠大體分爲2部分:只讀部分和讀寫部分。只讀部分包括.text
和.rodata
段,讀寫部分又根據任務的不一樣劃分紅了如下幾個段:html
代碼段也叫文本段或者文本,存儲了目標文件的一部分,或者包含虛擬地址空間中的可執行指定。其實就是存放了編譯程序代碼後機器指令。只讀。git
存儲了全部能夠修改的全局變量或者靜態變量,而且這些變量是已經賦了初始值的。程序員
未初始化全局變量和靜態變量。github
使用 malloc, realloc, 和 free 函數控制的變量,堆在全部的線程,共享庫,和動態加載的模塊中被共享使用。釋放工做由程序員控制,容易產生內存泄漏。objective-c
鏈表結構,從低地址向高地址生長的不連續內存區域,遍歷方向頁是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存,因此在大量對象建立但沒有及時釋放時會撐爆內存,因而可知,堆得到的空間比較靈活,也比較大。同時由於是鏈表結構,確定也會形成空間的不連續,從而形成大量的碎片,使程序效率下降。安全
內部臨時變量以及有關上下文的內存區域存放在棧中。程序在調用函數時,操做系統會自動經過壓棧和彈棧完成保存函數現場等操做,不須要程序員手動干預。bash
LIFO結構,從高地址向低地址生長。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,好比局部變量的分配。動態分配由alloca函數進行分配,可是棧的動態分配和堆是不一樣的,他的動態分配是由編譯器進行釋放,無需咱們手工實現。網絡
棧是一塊連續的內存區域,棧頂的地址和棧的最大容量是系統預先規定好的。能從棧得到的空間較小。若是申請的空間超過棧的剩餘空間時,例如遞歸深度過深,將提示stackoverflow。數據結構
棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。多線程
注意:在Objective-C中,函數內的臨時對象,對象指針是放在棧中,而對象數據其實存儲在堆中。 在針對一些場景,好比將這個臨時對象放到一個collection結構中時,在結束做用域時,在堆中更好處理。還有一部分則是歷史緣由能夠追溯到NeXTSTEP version 2.0時候的設計。 why does objective-c store objects on the heap instead of on the stack
int val = 3;
char string[] = "Hello World";
複製代碼
這兩個變量的值會一開始被存儲在 .text 中(由於值是寫在代碼裏面的),在程序啓動時會拷貝到 .data 去區中。
而不初始化的話,像下面這樣,這個變量就會被放在 bss 段中。
static int i;
複製代碼
iOS中使用了引用計數來管理內存,引用計數中包含兩種方式:MRC 和 ARC。這裏假設讀者使用過MRC而且有必定了解。
在MRC中有四個法則知道程序員手動管理內存:
alloc
、new
、copy
或者mutableCopy
開頭的方法建立的對象。(好比 alloc
,newObject
,mutableCopy
)release
或者autorelease
消息釋放本身持有的對象。release
和autorelease
在ARC中都再也不須要手動調用。四個法則對應的代碼:
/*
* 本身生成並持有該對象
*/
id obj0 = [[NSObeject alloc] init];
id obj1 = [NSObeject new];
複製代碼
/*
* 持有非本身生成的對象
*/
id obj = [NSArray array]; // 非本身生成的對象,且該對象存在,但本身不持有
[obj retain]; // 本身持有對象
複製代碼
/*
* 不在須要本身持有的對象的時候,釋放
*/
id obj = [[NSObeject alloc] init]; // 此時持有對象
[obj release]; // 釋放對象
/*
* 指向對象的指針仍就被保留在obj這個變量中
* 但對象已經釋放,不可訪問
*/
複製代碼
/*
* 非本身持有的對象沒法釋放
*/
id obj = [NSArray array]; // 非本身生成的對象,且該對象存在,但本身不持有
[obj release]; // ~~~此時將運行時crash 或編譯器報error~~~ 非 ARC 下,調用該方法會致使編譯器報 issues。此操做的行爲是未定義的,可能會致使運行時 crash 或者其它未知行爲
複製代碼
非本身持有的對象沒法釋放,這些對象何時釋放呢?這就要利用autorelease對象來實現的,autorelease對象不會在做用域結束時當即釋放,而是會加入autoreleasepool釋放池中,應用程序在事件循環的每一個循環開始時在主線程上建立一個autoreleasepool,並在循環最後調用drain將其排出,這時會調用autoreleasepool中的每個對象的release方法。
內存中的常量對象(類對象,常量字符串對象等)是在.data
或.bss
字段,他們沒有引用計數機制,永遠不能釋放這些對象。給這些對象發送消息retainCount
後,返回的是NSUIntergerMax。
init
和delloc
中使用setter
或者getter
方法若是存在繼承和子類重寫父類setter或者getter方法的前提下,可能會存在崩潰或異常狀態。
在dealloc裏不要調用屬性的存取方法,由於有人可能會覆寫這些方法,並於其中作一些沒法再回收階段安全執行的操做(上面已經提到)。此外,屬性可能正處於「鍵值觀察」(Key-Value Observation,KVO)機制的監控之下,該屬性的觀察者(Observer)可能會在屬性值改變時「保留」或使用這個即將回首的對象。這種作法會令運行期系統的狀態徹底失調,從而致使一些莫名其妙的錯誤。
對象delloc方法的調用可能存在的問題:
delloc
中及時處理。@property (assign/retain/strong/weak/unsafe_unretained/copy) NSArray *array;
複製代碼
assign
: 代表setter
僅僅是一個簡單的賦值操做,一般用於基本的數值類型,例如CGFloat和NSInteger。 retain
: 引用計數加1。 strong
: 和retain
相似,引用計數加1。對象類型時默認就是strong
。 weak
: 和assign
相似,當對象釋放時,會自動設置爲nil
。 unsafe_unretained
: 的語義和assign
相似,不過是用於對象類型的,表示一個非擁有(unretained)的,同時也不會在對象被銷燬時置爲nil
的(unsafe)關係。性能優於weak
參照weak
實現原理。 copy
: 相似storng
,不過在賦值時進行copy
操做而不是retain
,在setter
中比較明顯的會發現一個copy
行爲。 常在model或者賦值時使用,防止外部修改或者多線程中修改。
__strong
: 對象默認使用標識符,retain
+1。只要存在strong
指針指向一個對象那他就會一直保存存活。 __weak
: 弱引用對象,引用計數不會增長。對象被銷燬時本身被置爲 nil 。 __unsafe_unretained
: 不會持有對象,引用計數不會增長,可是在對象被銷燬時不會自動置爲nil,該指針依舊指向原來的地址,這就變成一個懸垂指針了。 __autoreleasing
: 用來表示id *
修飾的參數,而且在返回時被自動釋放掉。
// ClassName * qualifier variableName;
// for example:
MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;
複製代碼
qualifier
只能放在 * 和 變量名 之間,可是放到其餘位置也不會報錯,編譯器對此作過優化。
在使用引用地址傳值時須要特別注意,好比如下代碼能正常工做:
NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// Report the error.
// ...
}
複製代碼
可是,這裏有一個錯誤的隱式聲明: NSError * __strong error;
而方法的聲明是: -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
所以編譯器會重寫:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
// Report the error.
// ...
}
複製代碼
固然你也能夠建立-(BOOL)performOperationWithError:(NSError * __strong *)error;
方法,也能夠建立NSError * __autoreleasing error;
使他們的類型一致,採用何種方式視具體上下文邏輯而定。
使用引用計數管理內存時,不可避免的會遇到循環引用問題。 產生緣由是多個對象間存在相互引用,其中某個對象的釋放都依賴於另外一個對象的釋放,造成了一個獨立的環狀結構。
爲了打破這個循環引用關係,有如下兩種辦法:
weak
弱引用的方式,修飾對象。上面也有提到了AutoreleasePool,這在整個內存管理中扮演了很是重要的角色。 在NSAutoreleasePool文檔中:
In a reference counted environment, Cocoa expects there to be an autorelease pool always available. If a pool is not available, autoreleased objects do not get released and you leak memory. In this situation, your program will typically log suitable warning messages.
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event. If you use the Application Kit, you therefore typically don’t have to create your own pools. If your application creates a lot of temporary autoreleased objects within the event loop, however, it may be beneficial to create 「local」 autorelease pools to help to minimize the peak memory footprint.
autoreleasepool 和線程的關係 Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself. Threads If you are making Cocoa calls outside of the Application Kit’s main thread—for example if you create a Foundation-only application or if you detach a thread—you need to create your own autorelease pool.
If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should periodically drain and create autorelease pools (like the Application Kit does on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If, however, your detached thread does not make Cocoa calls, you do not need to create an autorelease pool.
文中提到了autoreleasepool4個比較特別的狀況:
若是autoreleasepool無效時,autorelease對象是沒法收到release消息,從而形成內存泄漏。在ARC狀況下不多會出現autoreleasepool無效的狀況下。
對於須要產生大量臨時autorelease對象的邏輯,須要使用**@autoreleasepool{}**來當即釋放來下降內存的峯值。
關於autoreleasepool在線程中的線程的佈局,官方文檔說每個線程都會在棧中維護建立的NSAutoreleasePool 對象,而且會把這個對象放到棧的頂部,從而確保在線程結束時autoreleasepool能在最後drain而且dealloc後從棧中移除。
autoreleasepool與線程的關係,除了main thread外其餘線程都沒有自動生成的autoreleasepool。若是你的線程須要長時間存活或者會有autorelease對象生成,就必需要在線程一開始就建立autoreleasepool,不然就會有對象泄漏。尤爲是長時間存活的線程,你還須要像主線程在runloop末尾定時的去drain。
delloc
方法調用狀況。《Apple About Memory Management》
《Clang中ARC的實現》
《黑幕背後的 Autorelease》
《內存管理》