iOS內存管理佈局及管理方案-理論篇

蘋果設備備受歡迎的背後離不開iOS優秀的內存管理機制,那iOS的內存佈局及管理方案是怎樣的呢?咱們一塊兒研究下。

內存管理分爲五大塊

棧區(stack):線性結構,內存連續,系統本身管理內存,程序運行記錄,每一個線程,也就是每一個執行序列各有一個(看crash log最容易理解),都是編譯的時候能肯定好的,還有一個特色就是這裏面的數據能夠不用指針,也不會丟。編程

堆區(heap):鏈式結構,內存不連續,最靈活的內存區,用途多多,動態分配和釋放,編譯時不能提早肯定,咱們的Objective-C對象都是這麼來的,都存在這裏,一般堆中的對象都是以指針來訪問的,指針從線程棧中來,但不獨屬於某個線程,堆也是對複雜的運行時處理的基礎支持,還有就是ARC仍是MRC、「誰分配誰釋放」說的都是堆上對象的管理。swift

靜態區(全局區)(bss):初始化數據,簡單理解就是有初始值的變量、常量。數組

常量區(data):未初始化數據,只聲明未給值的變量,運行前通通爲0,之因此單獨分出來,是出於性能的考慮,由於這些東西都是0,不必放在程序包裏,也不用copy。數據結構

代碼區(text):最靜態的,就是隻讀的東西,存儲代碼。多線程

iOS內存管理方案有三種

咱們詳細看下每種方案的實現及存在的意義。ide

一.tagged pointer佈局

沒有這種管理機制會引發內存浪費,爲何呢?咱們來看下,假設咱們要存儲一個NSNumber對象,其值是一個整數。正常狀況下,若是這個整數只是一個NSInteger的普通變量,那麼它所佔用的內存是與CPU的位數有關,在32位CPU下佔4個字節,在64位CPU下是佔8個字節的。而指針類型的大小一般也是與CPU位數相關,一個指針所佔用的內存在32位CPU下爲4個字節,在64位CPU下也是8個字節。性能

因此一個普通的iOS程序,若是沒有Tagged Pointer對象,從32位機器遷移到64位機器中後,雖然邏輯沒有任何變化,但這種NSNumber、NSDate一類的對象所佔用的內存會翻倍。以下圖所示:spa

咱們再來看看效率上的問題,爲了存儲和訪問一個NSNumber對象,咱們須要在堆上爲其分配內存,另外還要維護它的引用計數,管理它的生命期。這些都給程序增長了額外的邏輯,形成運行效率上的損失。操作系統

爲了改進上面提到的內存佔用和效率問題,蘋果提出了Tagged Pointer對象。因爲NSNumber、NSDate一類的變量自己的值須要佔用的內存大小經常不須要8個字節,拿整數來講,4個字節所能表示的有符號整數就能夠達到20多億(注:2^31=2147483648,另外1位做爲符號位),對於絕大多數狀況都是能夠處理的。

因此咱們能夠將一個對象的指針拆成兩部分,一部分直接保存數據,另外一部分做爲特殊標記,表示這是一個特別的指針,不指向任何一個地址。因此,引入了Tagged Pointer對象以後,64位CPU下NSNumber的內存圖變成了如下這樣:

當8字節能夠承載用於表示的數值時,系統就會以Tagged Pointer的方式生成指針,若是8字節承載不了時,則又用之前的方式來生成普通的指針。以上是關於Tag Pointer的存儲細節。

Tagged Pointer的特色:

1. 咱們也能夠在WWDC2013的《Session 404 Advanced in Objective-C》視頻中,看到蘋果對於Tagged Pointer特色的介紹:Tagged Pointer專門用來存儲小的對象,例如NSNumber和NSDate, 固然NSString小於60字節的也能夠運用了該手段

2. Tagged Pointer指針的值再也不是地址了,而是真正的值。因此,實際上它再也不是一個對象了,它只是一個披着對象皮的普通變量而已,由於他沒有isa指針。因此,它的內存並不存儲在堆中,也不須要malloc和free。

3. 在內存讀取上有着3倍的效率,建立時比之前快106倍。

因而可知,蘋果引入Tagged Pointer,不但減小了64位機器下程序的內存佔用,還提升了運行效率。完美地解決了小內存對象在存儲和訪問效率上的問題。

2、Non-pointer iSA--非指針型iSA

在64位系統上只須要32位來儲存內存地址,而剩下的32位就能夠用來作其餘的內存管理

non_pointer iSA 的判斷條件:

1 : 包含swift代碼;

2:sdk版本低於10.11;

3:runtime讀取image時發現這個image包含__objc_rawi sa段;

4:開發者本身添加了OBJC_DISABLE_NONPOINTER_ISA=YES到環境變量中;

5:某些不能使用Non-pointer的類,GCD等;

6:父類關閉。

3、SideTables,RefcountMap,weak_table_t

爲了管理全部對象的引用計數和weak指針,蘋果建立了一個全局的SideTables,雖然名字後面有個"s"不過他實際上是一個全局的Hash表,裏面的內容裝的都是SideTable結構體而已。它使用對象的內存地址當它的key。管理引用計數和weak指針就靠它了。

由於對象引用計數相關操做應該是原子性的。否則若是多個線程同時去寫一個對象的引用計數,那就會形成數據錯亂,失去了內存管理的意義。同時又由於內存中對象的數量是很是很是龐大的須要很是頻繁的操做SideTables,因此不能對整個Hash表加鎖。蘋果採用了分離鎖技術。

下邊是SideTabel的定義:

SideTable
   struct SideTable {
     //鎖
     spinlock_t slock;
     //強引用相關
     RefcountMap refcnts;
     //弱引用相關
     weak_table_t weak_table;
     ...
     }

當咱們經過SideTables[key]來獲得SideTable的時候,SideTable的結構以下:

一、一把自旋鎖。spinlock_t slock;

自旋鎖比較適用於鎖使用者保持鎖時間比較短的狀況。正是因爲自旋鎖使用者通常保持鎖時間很是短,所以選擇自旋而不是睡眠是很是必要的,自旋鎖的效率遠高於互斥鎖。信號量和讀寫信號量適合於保持時間較長的狀況,它們會致使調用者睡眠,所以只能在進程上下文使用,而自旋鎖適合於保持時間很是短的狀況,它能夠在任何上下文使用。

它的做用是在操做引用技術的時候對SideTable加鎖,避免數據錯誤。

蘋果在對鎖的選擇上能夠說是精益求精。蘋果知道對於引用計數的操做實際上是很是快的。因此選擇了雖然不是那麼高級可是確實效率高的自旋鎖

二、引用計數器 RefcountMap *refcnts;

對象具體的引用計數數量是記錄在這裏的。

這裏注意RefcountMap實際上是個C++的Map。爲何Hash之後還須要個Map呢?由於內存中對象的數量實在是太龐大了咱們經過第一個Hash表只是過濾了第一次,而後咱們還須要再經過這個Map才能精確的定位到咱們要找的對象的引用計數器。

引用計數器的數據類型是:

typedef __darwin_size_t        size_t;

再進一步看它的定義實際上是unsigned long,在32位和64位操做系統中,它分別佔用32和64個bit。

蘋果常用bit mask技術。這裏也不例外。拿32位系統爲例的話,能夠理解成有32個盒子排成一排橫着放在你面前。盒子裏能夠裝0或者1兩個數字。咱們規定最後邊的盒子是低位,左邊的盒子是高位。

(1UL<<0)的意思是將一個"1"放到最右側的盒子裏,而後將這個"1"向左移動0位(就是原地不動):0b0000 0000 0000 0000 0000 0000 0000 0001

(1UL<<1)的意思是將一個"1"放到最右側的盒子裏,而後將這個"1"向左移動1位:0b0000 0000 0000 0000 0000 0000 0000 0010

下面來分析引用計數器(圖中右側)的結構,從低位到高位。

(1UL<<0)????WEAKLY_REFERENCED

表示是否有弱引用指向這個對象,若是有的話(值爲1)在對象釋放的時候須要把全部指向它的弱引用都變成nil(至關於其餘語言的NULL),避免野指針錯誤。

(1UL<<1)????DEALLOCATING

表示對象是否正在被釋放。1正在釋放,0沒有

(1UL<<(WORD_BITS-1))????SIDE_TABLE_RC_PINNED

其中WORD_BITS在32位和64位系統的時候分別等於32和64。其實這一位沒啥具體意義,就是隨着對象的引用計數不斷變大。若是這一位都變成1了,就表示引用計數已經最大了不能再增長了。

三、維護weak指針的結構體 weak_table_t *weak_table;

第一層結構體中包含兩個元素。

第一個元素weak_entry_t *weak_entries;是一個數組,上面RefcountMap是要經過find(key)來找到精確的元素的。weak_entries則是經過循環遍從來找到對應的entry。

(上面管理引用計數器蘋果使用的是Map,這裏管理weak指針蘋果使用的是數組,有興趣的朋友能夠思考一下爲何蘋果會分別採用這兩種不一樣的結構)

這個是由於weak的顯著的特徵來決定的: 當weak對象被銷燬的時候,要把全部指向該對象的指針都設爲nil。

第二個元素num_entries是用來維護保證數組始終有一個合適的size。好比數組中元素的數量超過3/4的時候將數組的大小乘以2。

第二層weak_entry_t的結構包含3個部分

一、referent:被指對象的地址。前面循環遍歷查找的時候就是判斷目標地址是否和他相等。

二、referrers:可變數組,裏面保存着全部指向這個對象的弱引用的地址。當這個對象被釋放的時候,referrers裏的全部指針都會被設置成nil。

三、inline_referrers只有4個元素的數組,默認狀況下用它來存儲弱引用的指針。當大於4個的時候使用referrers來存儲指針。

上面咱們介紹了蘋果爲了更好的內存管理使用的三種不一樣的內存管理方案,在內部採用了不一樣的數據結構以達到更高效內存檢索。

參考連接: https://www.jianshu.com/p/dcbf48a733f9

http://www.cocoachina.com/articles/13449

http://www.cocoachina.com/articles/24119

參考書籍:Objective-C高級編程:iOS與OS X多線程和內存管理

歡迎點擊「京東雲」瞭解更多精彩內容

相關文章
相關標籤/搜索