iOS-內存管理(一)-佈局&方案

內存佈局

  • 棧區:由編譯器分配和釋放,是一種從高地址向低地址擴展的數據結構,是不連續的內存區域,先進後出(FILO)。主要用於存儲函數、方法、指針、臨時變量,簡單類型的變量。優勢是效率高。
  • 堆區:在運行時由程序員分配和釋放,若程序員不釋放,程序結束時可能由系統回收。堆是一種從低地址向高地址擴展的數據結構,是不連續的內存區域,以鏈表的方式進行存儲。經過alloc生成的對象,或者block拷貝的對象等。相比棧區效率較低。
  • .bbs區:靜態區域,系統分配和釋放,用來存儲未初始化的全局變量、靜態變量。
  • .data區:系統分配和釋放,用來存儲初始化的全局變量、靜態變量。
  • .text區:系統分配和釋放,程序加載到內存中存儲的區域。
- (void)test {
    // 存放放在棧區
    int a = 10
    
    // &obj存放在棧區
    // obj存放在堆區
    NSObject *obj = [NSObject alloc];
}
複製代碼

棧效率比堆高的緣由以下:c++

  1. 有寄存器直接對棧進行訪問,而對堆訪問,只能是間接尋址。
  2. 棧中數據cpu命中率更高,知足局部性原理。
  3. 棧是編譯時系統自動分配空間,而堆是動態分配(運行時分配空間),因此棧的速度快。
  4. 棧是先進後出的隊列結構,比堆結構相對簡單,分配速度大於堆。

局部變量

在函數內部聲明,做用域只在當前{}以內。 存儲位置:自動保存在函數的每次執行的【棧幀】中,並隨着函數結束後自動釋放。程序員

全局變量

全局變量是一種在函數外聲明、能夠跨文件訪問的變量。它能夠在聲明時賦值,也能夠在使用的時候賦值,若是在聲明是沒有賦值,系統會默認分配爲空值。聲明方式以下:bash

NSString *global;
NSString *global = @"AAA";
複製代碼

全局變量保存在內存的全局存儲區中,佔用靜態的存儲單元;局部變量保存在棧中,只有在所在函數被調用時才動態地爲變量分配存儲單元。數據結構

咱們能夠在block內部直接訪問全局變量,而不須要藉助其餘修飾詞。多線程

使用全局變量不能在.h中定義,別的文件導入當前文件的時候會報錯,duplicate symbols for architecture x86_64。由於在編譯階段,頭文件的信息都會copy到文件中,這樣的話會出現全部引入的頭文件的都會有這麼一個全局變量,因此會出現重名的狀況。解決這種問題,咱們能夠將其定義在.m文件中,而後在須要使用的地方使用extern關鍵字獲取。架構

static

  • static關鍵字修飾局部變量時,只會初始化一次,在內存中只有一塊地址。
- (void)staticLocalTest {
    for (int i = 0; i < 5; i++) {
        static int num = 0;
        num += 1;
        NSLog(@"==static num==%d==", num);
    }
}
複製代碼

能夠看出,被static修飾的變量,延長了生命週期,本該在for循環中每一次循環就銷燬,實際上只會初始化一次,在for循環結束以後才銷燬。async

  • static關鍵字修飾全局變量時,若是是在.m文件中,做用域僅限於當前文件,外部類是不能夠訪問到該全局變量的;若是是在.h文件中,能夠被任意引入當前文件做爲頭文件的文件所使用。

靜態全局變量

static 類型 變量名;
複製代碼

靜態全局變量在每一個文件中都會單獨拷貝一份地址,互不影響。好比我在文件A中定義了一份靜態全局變量,分別在ABC三個文件中使用,他們的賦值是互不影響的。而全局變量是隻有一份地址,這是它們的不一樣點。ide

內存管理方案

iOS系統根據使用場景的不一樣,提供了3種方案:函數

  • TaggedPointer:對於一些小對象,如NSNumberNSDate等採用此種方案
  • NONPOINTER_ISA:64位架構下iOS應用程序
  • 散列表:散列表爲複雜的數據結構,包含了引用計數表和弱引用表

1. TaggedPointer

TaggedPointer是蘋果從64bit開始使用的一項內存管理技術,用於優化NSNumberNSDateNSString等小對象的存儲。在沒有使用TaggedPointer以前,這些小對象須要動態分配內存、維護引用計數等。使用了Tagged Pointer,指針的值再也不是地址了,而是真正的值。實際上它再也不是一個對象了,而是一個普通變量而已。因此,它的內存並不存儲在堆中,不須要malloc申請、或者是釋放。當指針不夠存儲數據時,纔會使用動態分配內存的方式來存儲數據。佈局

如:沒有使用TaggedPointer的時候,NSNumber指針存儲的是堆中NSNumber對象的地址值。使用TaggedPointer以後,NSNumber指針裏面存儲的數據變成了:Tag + Data,也就是將數據直接存儲在了指針中。

經過下面例子咱們來看一下系統對TaggedPointer的特殊處理:

- (void)taggedpointerTest {
    self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"abc"];
             NSLog(@"%@",self.nameStr);
        });
    }
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"carshcarshcarshcarshcarshcarshcarshcarshcarshcarsh"];
            NSLog(@"%@",self.nameStr);
        });
    }
}
複製代碼

運行程序,第二個for循環會崩潰,提示objc_release異常。崩潰的緣由是能夠看到是由於多線程訪問setter/getter,那麼爲何第一個for循環沒有崩潰呢?從代碼看兩個for循環的區別就是第一個nameStr賦值短,第二個長一些。打斷點,在控制檯查看信息,能夠看到第一個循環中的nameStrNSTaggedPointerString *,而第二個是NSCFString *。查看retain/release的源碼,能夠看到:

id objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

id objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}
複製代碼

剛纔咱們說了,TaggedPointer的存儲是類型+數據,那麼咱們來具體看一下,它的存儲策略:

  • 指針地址首位肯定類型,用來區分NSNumberNSDateNSString。如,0xb表明NSNumber
  • 若是是字符串,最後一位存儲字符串長度
  • 其他位利用ASCII碼編碼值來存儲字符

2. NONPOINTER_ISA

NONPOINTER_ISAMACOSX_VERSION_10_11以後出來的對isa內存的優化。在isa中添加了更多的信息。

uintptr_t nonpointer        : 1;                                    
uintptr_t has_assoc         : 1;                                    
uintptr_t has_cxx_dtor      : 1;                                    
uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic             : 6;                                    
uintptr_t weakly_referenced : 1;                                    
uintptr_t deallocating      : 1;                                    
uintptr_t has_sidetable_rc  : 1;                                    
uintptr_t extra_rc          : 19
複製代碼
  • nonpointer:表示是否對isa指針開啓指針優化。0表示純isa指針,11表示不止是類對象地址,isa中包含了類信息、對象的引用計數等。
  • has_assoc:關聯對象標誌位,0沒有,1存在。
  • has_cxx_dtor:該對象是否有c++或者objc的析構器,若是有析構函數,則須要作析構邏輯,若是沒有,則能夠更快的釋放對象。
  • shiftcls:存儲類指針的值。開啓指針優化的狀況下,在arm64架構中有33位用來存儲類指針。
  • magic:用於調試器判斷當前對象是真的對象仍是沒有初始化的空間。
  • weakly_referenced:志對象是否被指向或者曾經指向一個ARC的弱變量,沒有弱引用的對象能夠更快釋放。
  • deallocating:標誌對象是否正在釋放內存。
  • has_sidetable_rc:當對象引用技術大於10時,則須要借用該變量存儲進位。
  • extra_rc:當表示該對象的引用計數值,其實是引用計數值減1, 例如,若是對象的引用計數爲10,那麼extra_rc爲9。若是引用計數大於10,則須要使用到has_sidetable_rc

3. 散列表

散列表是一張哈希結構的表,其包含了自旋鎖、引用計數表、弱引用表。其中的引用計數表,就是來對內存管理作處理的。

相關文章
相關標籤/搜索