探究 iOS 記憶體問題

杭城小劉發表於2022-12-30
本文從 Tagged Pointer、objc 原始碼、dealloc 原理、AutoreleasePool 原理、野指標探究等技術點展開聊了聊 iOS 記憶體相關問題。

定時器記憶體洩漏

NSTimer、CADisplayLink 的 基礎 API [NSTimer scheduledTimersWithTimeInterval:1 repeat:YES block:nil] 和當前的 VC 都會互相持有,造成環,會存在記憶體洩漏問題。

定時器記憶體洩漏原因,解決方案以及高精度定時器,具體可以看這篇 NSTimer 中的記憶體洩露

iOS 記憶體佈局

棧、堆、BSS、資料段、程式碼段

棧(stack):又稱作堆疊,用來儲存程式的區域性變數(但不包括static宣告的變數,static修飾的資料存放於資料段中)。除此之外,在函式被呼叫時,棧用來傳遞引數和返回值。棧記憶體地址越來越少

func a {
    變數 1 地址最大
    變數 2 地址第二大
    // ...
    變數n  地址最小
}

堆(heap):用於儲存程式執行中被動態分配的記憶體段,它的大小並不固定,可動態的擴張和縮減。操作函式(malloc/free)。分配的記憶體空間地址越來越大。

BSS段(bss segment):通常用來儲存程式中未被初始化的全域性變數和靜態變數的一塊記憶體區域。BSS是英文Block Started by Symbol的簡稱。BSS段輸入靜態記憶體分配

資料段(data segment):通常用來儲存程式中已被初始化的全域性變數和靜態變數和字串的一塊記憶體區域。資料段包含3部分:

  • 字串常量。比如 NSString *str = @"杭城小劉";
  • 已初始化資料:已經初始化的全域性變數、靜態變數等
  • 未初始化資料:未初始化的全域性變數、靜態變數等

程式碼段(code segment):編譯之後的程式碼。通常是指用來儲存程式可執行程式碼的一塊記憶體區域。這部分割槽域的大小在程式執行前就已經確定,並且記憶體區域通常屬於只讀,某些架構也允許程式碼段為可寫,即允許修改程式。

上 Demo 驗證

int a = 10;
static int b;
int main () {
    NSString *name = @"杭城小劉";
    int age = 27;
    int height = 177;
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"\na: %p\nb: %p\n name: %p\nage: %p\n height: %p\nobj:%p", &a, &b, &name, &age, &height, obj);
}
a: 0x107b09b80
b: 0x107b09c48
name: 0x7ff7b83fdbc0
age: 0x7ff7b83fdbbc
height: 0x7ff7b83fdbb8
obj:0x6000012780e0

我們按照記憶體地址由低到高排個序(如下),發現和我們總結的規律一致。

// 字串常量
name:   0x7ff7b83fdbc0
// 已初始化的全域性變數、靜態變數
a:      0x107b09b80
// 未初始化的全域性變數、靜態變數
b:      0x107b09c48
// 堆
obj:    0x6000012780e0
// 棧
height: 0x7ff7b83fdbb8
age:    0x7ff7b83fdbbc
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%p %p %@", obj, &obj, obj);

分別列印 obj指標指向的堆上的記憶體地址、obj 指標在棧上的地址、obj 內容

Tagged Pointer

先來一個 Demo 開啟本部分內容(畫外音:程式碼很短,但讓我產生了一個大大的問號)

- (bool)isTaggedPointer:(const void *)ptr
{
    return ((uintptr_t)ptr & (1UL<<63)) == (1UL<<63);
}

NSNumber *number = [NSNumber numberWithInt:10]; // 0xb0000000000000a2 b:12  1100
NSLog(@"%p %d %@", number, [self isTaggedPointer:(__bridge const void *)number], number.class);

NSString *name1 = [NSString stringWithFormat:@"ss"]; // 0xa000000000073732 a:11 1011
NSLog(@"%p %d %@", name1, [self isTaggedPointer:(__bridge const void *)name1], name1.class);

前提說明:

建立一個 NSNumer 型別的變數 number,NSString 型別的 name1,程式碼列印地址、型別。產生一個問題:為什麼 NSNumber 是 TaggedPointer,但是 class 卻顯示 __NSCFNumber ?

static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) 
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
  • 透過 objc4 原始碼研究寫了個判斷物件是否是 Tagged Pointer 型別的方法。透過系統原始碼參考寫了判斷方法 isTaggedPointer。呼叫方法得到 number 物件是 Tagged Pointer 型別
  • 根據 iOS 平臺特性,根據記憶體地址高位分析確實是 TaggedPointer 型別 ​
  • 同樣的 NSString 指標指向的字串內容比較少,佔用記憶體沒必要開創新的記憶體時,name1 就是 NSTaggedPointerString,列印出 class 也是 NSTaggedPointerString。呼叫 isTaggedPointer 得到也是 Tageed Pointer 型別

帶著問題開始吧

什麼是 Tagged Pointer

iOS 從 64bit 開始引入了Tagged Pointer 技術,用於最佳化 NSNumber、NSDate、NSString等小物件的儲存。

在此之前,建立物件需要動態分配記憶體、維護引用計數等,物件指標儲存的是堆中物件的地址值建立一個物件的流程。先在堆上申請一塊記憶體,然後再在棧上增加一個指標型別,指標指向堆上這塊記憶體。假如是 NSNumber *value = [NSNumber numberWithInt:2] value 是指標長度為8位元組,堆上記憶體16位元組。加起來24位元組就存一個int 2。

此外還需要維護引用技術,沿用一個真正物件那一套,太大材小用了。

Tagged Pointer 格式,物件指標裡面儲存的資料變成了:Tag + Data,將資料直接儲存在了指標中。當指標不夠儲存資料時,才會使用動態分配記憶體的方式來儲存資料

objc_msgSend 能識別 Tagged Pointer,比如 NSNumber 的 intValue 方法,直接從指標提取資料,節省了呼叫開銷。

經典問題

Demo1

- (void)test {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (NSInteger i = 0; i<1000; i++) {
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"和好多好多好多好多事看看上課上課上課"];
        });
    }
}

執行該程式碼會 Crash,報錯資訊如下

說明:一開始的報錯資訊只說壞記憶體訪問,但是並沒有顯示具體的方法呼叫堆。想知道具體 Crash 原因還是需要看看堆疊比較方便。輸入 bt 檢視最後是由於 objc_release 方法造成 crash。

小竅門:利用 LLDB 模式下輸入 bt,可以檢視堆疊。也就是 backtrace 的縮寫。

不仔細想可能發現不了問題,看到 objc_release 就會想到是在多執行緒情況下 NSString 的 setter 方法內,ARC 程式碼經過編譯器最後會按照 MRC 去執行。所以 Setter 類似下面程式碼。

-(void)setName:(NSString *)name 
{
    if (_name!=name) {
        [name retain];
        [_name release];
        _name = name;
    }
}

Demo

- (void)test {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (NSInteger i = 0; i<1000; i++) {
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"ss"];
            if (i == 100) {
                NSLog(@"%p %@", self.name, self.name.class);
            }
        });
    }
} 
// 0xa000000000073732 NSTaggedPointerString

同樣的程式碼字串變短居然不 crash 了?因為命中 Tagged Pointer 邏輯了,檢視型別是 NSTaggedPointerString

本問題本質是

  • ARC 程式碼在編譯後真正執行階段是走 MRC 的,strong、copy 內部都會 release 舊的,copy/retain 新的
  • 多執行緒情況下訪問 setter 需要加鎖
  • 字串在 NSTaggedPointerString 情況下不存在像 OC 物件的 setter 方法內的 release、copy 操作

如何判斷一個指標是否為Tagged Pointer

檢視 objc4 原始碼

#if TARGET_OS_OSX && __x86_64__
    // 64-bit Mac - tag bit is LSB
#   define OBJC_MSB_TAGGED_POINTERS 0
#else
    // Everything else - tag bit is MSB
#   define OBJC_MSB_TAGGED_POINTERS 1
#endif

#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#   define _OBJC_TAG_INDEX_SHIFT 60
#   define _OBJC_TAG_SLOT_SHIFT 60
#   define _OBJC_TAG_PAYLOAD_LSHIFT 4
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK (0xfUL<<60)
#   define _OBJC_TAG_EXT_INDEX_SHIFT 52
#   define _OBJC_TAG_EXT_SLOT_SHIFT 52
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#else
#   define _OBJC_TAG_MASK 1UL
#   define _OBJC_TAG_INDEX_SHIFT 1
#   define _OBJC_TAG_SLOT_SHIFT 0
#   define _OBJC_TAG_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK 0xfUL
#   define _O  BJC_TAG_EXT_INDEX_SHIFT 4
#   define _OBJC_TAG_EXT_SLOT_SHIFT 4
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#endif

static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) 
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

可以看到原始碼透過 _objc_isTaggedPointer 方法判斷是否是 Tagged Pointer 型別。傳入物件地址,內部透過 _OBJC_TAG_MASK 按位與運算。

其中 _OBJC_TAG_MASK 是一個宏,宏定義外部有個 if 判讀,判斷是 Mac OS 並且是 x86_64 架構則為0,否則為1。也就是 Mac OS 並且是 x86_64 架構情況下則與 1UL 按位與,否則與 1UL<<63 按位與。

  • iOS平臺 | Mac 非 x86 平臺: 最高有效位是1(第64bit)1UL<<63
  • Mac 且 x86平臺: 最低有效位是11UL

比如 iOS 平臺下

0xb0000000000000a2 b:12  1100

  1100
& 1000
-------
  1000

tips:某些物件雖然是 TaggedPointer 型別,但是列印 class 發現不是,猜測可能是系統用類簇隱藏了某些實現細節。比如下面

針對 NSNumber 的 TaggedPoniter 的 case,檢視 class 列印出 __NSCFNumber。但根據原始碼和記憶體高地址位分析確實是 TaggedPoniter。

疑問是:為什麼 NSNumber 的 TaggedPpinter case 下列印 class 是 __NSCFNumber。如果是類簇隱藏細節實現,為什麼同樣 KVO 也改變了 isa,但是命名是一個新的名字,而不是類簇的實現?

和朋友討論後有2種觀點(觀點不是獨立的,而是並且同時成立的。對錯難以判定,僅供參考):

  • 類簇,為了隱藏細節實現
  • KVO 和當前 case 不一致。類簇是系統類的設計,KVO 是針對開發者寫的物件所以沒有類簇,只能動態生成類,改變原類的 isa,命名為 NSKVONotifying_*** 這樣的規則。

類簇

類簇(Class Cluster )是抽象工廠模式在 OC 陣列中的實現,NSArray、NSNumber、NSString、NSDictionary 都有體現。藉口簡單性和擴充性的權衡體現。系統隱藏了較多實現細節,只暴露出簡單介面。

- (void)classCus
{
    id obj1 = [NSArray alloc]; // __NSPlaceholderArray
    id obj2 = [NSMutableArray alloc]; // __NSPlaceholderArray
    id obj3 = [obj1 init]; // __NSArray0
    id obj4 = [obj2 init]; // __NSArrayM
    NSLog(@"%@ %@ %@ %@", obj1, obj2, obj3, obj4);
}

呼叫 alloc 之後產生的是 __NSPlaceholderArray 不符合預期。繼續呼叫 init 發現滿足期望了。所以猜測 __NSPlaceholderArray  是一箇中間物件,後續的 init 方法就是給中間物件發訊息,再由它做工廠,生成真的物件,這裡的 __NSArray0__NSArrayM 對應 NSArray、NSMutableArray

Foundation用了靜態例項地址方式來實現,虛擬碼如下:

static __NSPlacehodlerArray *GetPlaceholderForNSArray() {
    static __NSPlacehodlerArray *instanceForNSArray;
    if (!instanceForNSArray) {
        instanceForNSArray = [[__NSPlacehodlerArray alloc] init];
    }
    return instanceForNSArray;
}

static __NSPlacehodlerArray *GetPlaceholderForNSMutableArray() {
    static __NSPlacehodlerArray *instanceForNSMutableArray;
    if (!instanceForNSMutableArray) {
        instanceForNSMutableArray = [[__NSPlacehodlerArray alloc] init];
    }
    return instanceForNSMutableArray;
}
// NSArray實現
+ (id)alloc {
    if (self == [NSArray class]) {
        return GetPlaceholderForNSArray()
    }
}
// NSMutableArray實現
+ (id)alloc {
    if (self == [NSMutableArray class]) {
        return GetPlaceholderForNSMutableArray()
    }
}
// __NSPlacehodlerArray實現
- (id)init {
    if (self == GetPlaceholderForNSArray()) {
        self = [[__NSArrayI alloc] init];
    }
    else if (self == GetPlaceholderForNSMutableArray()) {
        self = [[__NSArrayM alloc] init];
    }
    return self;
}

另外 iOS Foundation 對靜態不可變空物件(當前 case 為陣列)做了最佳化

NSArray *a1 = [[NSArray alloc] init];
NSArray *a2 = [[NSArray alloc] init];
NSArray *a3 = [[NSArray alloc] init];
(lldb) p a1
(__NSArray0 *) $0 = 0x0000000109f50a10 @"0 elements"
(lldb) p a2
(__NSArray0 *) $1 = 0x0000000109f50a10 @"0 elements"
(lldb) p a3
(__NSArray0 *) $2 = 0x0000000109f50a10 @"0 elements"
(lldb) 

若干個不可變的空陣列間沒有任何特異性,返回一個靜態物件。

OC 物件記憶體管理

iOS 中使用引用計數來管理 OC 物件的記憶體。一個新建立的 OC 物件引用計數預設是1,當引用計數減為 0,OC 物件就會銷燬,釋放其佔用的記憶體空間

呼叫 retain/copy 會讓 OC 物件的引用計數 +1,呼叫 release 會讓 OC 物件的引用計數 -1。

記憶體管理的經驗總結

  • 當呼叫 alloc、new、copy、mutableCopy 方法返回了一個物件,在不需要這個物件時,要呼叫 release 或者 autorelease 來釋放它
  • 想擁有某個物件,就讓它的引用計數 +1;不想再擁有某個物件,就讓它的引用計數 -1
  • 可以透過以下私有函式來檢視自動釋放池的情況extern void _objc_autoreleasePoolPrint(void);

殭屍物件:重複釋放記憶體造成的。一個典型場景是多次 setter。setter 內部實現不合理,比如下面 setter。

Person *p = [[Person aloc] init]; // 1
Cat *cat = [[Cat alloc] init]; // 1  
[p setCat:cat];  // 2
[cat release]; // 1
[p setCat:cat]; // 0
[p setCat:cat]; // badAccess
- (void)setCat:(Cat *)cat
{
    [_cat release];
    _cat = [cat retain];
}

改進

- (void)setCat:(Cat *)cat
{    
    if (_cat != cat) {
        [_cat release];
        _cat = [cat retain];
    }
} 

早期在 MRC 時代,在 .h 檔案中 @property 只會屬性的 getter、setter 宣告,@synthesize 會自動生成成員變數和屬性的 setter、getter 的實現。隨著編譯器進步,現在 @property 會做完全部的事情。

早期 VC 中使用屬性

@property (nonatomic, strong) NSMutableDictionary *dict;

NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
self.dict = dict;
[dict release];

透過 Foundation 框架中類方法建立出來的物件,會自動呼叫 autorelease 方法。

簡寫為 self.dict = [NSMutableDictionary dictionary];

上述可以檢視 GUNStep 原始碼  NSDictionary.m

#define    AUTORELEASE(object)    [(id)(object) autorelease]
+ (id) dictionary {
  return AUTORELEASE([[self allocWithZone: NSDefaultMallocZone()] init]);
}

QA:ARC 做了什麼

ARC 其實是 LLVM + Runtime 共同作用的結果。LLVM 編譯器自動插入 retain、release 記憶體管理程式碼。Runtime 執行時幫我們處理類似 __weak 程式執行過程中弱引用清除掉。

copy/mutableCopy

OC 有2個複製方法

  • copy 不可變複製,產生新不可變物件
  • mutableCopy 可變複製,產生新可變物件

上個 Demo1

NSArray *array1 = [[NSArray alloc] initWithObjects:@"1", @"2", nil];
NSLog(@"array1 --- %zd", array1.retainCount);
NSArray *array2 = [array1 copy];
NSLog(@"array1 --- %zd", array1.retainCount);
NSLog(@"array2 --- %zd", array2.retainCount);
NSMutableArray *array3 = [array1 mutableCopy];
NSLog(@"array1 --- %zd", array1.retainCount);
NSLog(@"array2 --- %zd", array2.retainCount);
NSLog(@"array3 --- %zd" array3.retainCount);

[array3 release];
NSLog(@"array3 --- %zd", array3.retainCount);
[array2 release];
NSLog(@"array2 --- %zd", array2.retainCount);
NSLog(@"array1 --- %zd", array1.retainCount);
[array1 release];
NSLog(@"array1 --- %zd", array1.retainCount);
2022-04-12 20:50:43.639296+0800 Main[4408:60897] array1 --- 1
2022-04-12 20:50:43.639715+0800 Main[4408:60897] array1 --- 2
2022-04-12 20:50:43.639772+0800 Main[4408:60897] array2 --- 2
2022-04-12 20:50:43.639846+0800 Main[4408:60897] array1 --- 2
2022-04-12 20:50:43.639899+0800 Main[4408:60897] array2 --- 2
2022-04-12 20:50:43.639957+0800 Main[4408:60897] array3 --- 1
2022-04-12 20:50:43.640013+0800 Main[4408:60897] array3 --- 0
2022-04-12 20:50:43.640059+0800 Main[4408:60897] array2 --- 1
2022-04-12 20:50:43.640105+0800 Main[4408:60897] array1 --- 1
2022-04-12 20:50:43.640159+0800 Main[4408:60897] array1 --- 0

疑問1: 為什麼在 array2 建立之後 array2、array1 的引用技術都是2.

因為 array1 指標指向堆上一塊記憶體(NSArray 型別),建立好後 array1 引用計數為1。在建立 array2 的時候發現是對 array1 的淺複製,系統為了記憶體的節省最佳化,array2 的指標也指向堆上的這一塊記憶體,copy 本身會對 array1 引用技術 +1,變為2。所以這時候 array2 指標指向的記憶體,引用計數也是2.

基於此,我們稍微修改下,看看 Demo2

NSArray *array1 = [[NSArray alloc] initWithObjects:@"1", @"2", nil];
NSLog(@"array1 --- %zd", array1.retainCount);
NSArray *array2 = [array1 mutableCopy];
NSLog(@"array1 --- %zd", array1.retainCount);
NSLog(@"array2 --- %zd", array2.retainCount);

2022-04-12 20:55:36.539060+0800 Main[4576:65031] array1 --- 1
2022-04-12 20:55:36.539514+0800 Main[4576:65031] array1 --- 1
2022-04-12 20:55:36.539631+0800 Main[4576:65031] array2 --- 1

因為 array1 指標指向堆上一塊記憶體(NSArray 型別),建立好後 array1 引用計數為1。在建立 array2 的時候發現是對 array1 的深複製,要產生不可變物件,所以堆上申請記憶體空間,array2 指標指向這塊記憶體,引用技術為1。

此外 mutableCopy 是 Foundation 針對集合類提供的。如果自定義物件需要支援 copy 方法,需遵循對應的NSCopyint 協議,實現協議方法 -(id)copyWithZone:(NSZone *)zone

總結:

NSStringNSMutableStringNSArrayNSMutableArrayNSDictionaryNSMutableDictionary
copyNSString 淺複製NSString 深複製NSArray 淺複製NSArray 深複製NSDictionary 淺複製NSDictionary 深複製
mutableCopyNSMutableString 深複製NSMutableString 深複製NSMutableArray 深複製NSMutableArray 深複製NSMutableDictionary 深複製NSMutableDictionary 深複製

引用計數

union isa_t {
    Class cls;
    uintptr_t bits;
    struct {
        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;
    };
}

iOS 從 64 位開始開始,對 isa 進行了最佳化,資訊存放於 union 結構中

  • extra_rc 儲存引用計數資訊-1,可以看到是 19位。儲存引用計數器 -1
  • has_sidetable_rc 引用計數是否過大無法儲存在 isa。當過大無法儲存與 isa 中時,has_sidetable_rc 這位會變為1,引用計數儲存在 SideTable 的類的屬性中

也就是說,iOS 從64位開始,引用計數存放於 isa 結構體的一個 union 中,欄位為 extra_rc,值為物件引用計數值 -1。當引用計數過大無法存放的時候 union 中 has_sidetable_rc 為 1,則引用計數存放於 SideTable 結構體中。

SideTable 結構如下

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
};

其中 refcnts 是一個存放著物件引用計數的雜湊表

檢視 objc4 關於引用計數的實現

uintptr_t _objc_rootRetainCount(id obj) {
    assert(obj);
    return obj->rootRetainCount();
}

inline uintptr_t objc_object::rootRetainCount() {
    if (isTaggedPointer()) return (uintptr_t)this;
    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) { // 最佳化過的 isa
        uintptr_t rc = 1 + bits.extra_rc; 
        if (bits.has_sidetable_rc) { // 引用計數不是儲存在 isa 中,而是 SideTable 
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }
    sidetable_unlock();
    return sidetable_retainCount();
}

size_t  objc_object::sidetable_getExtraRC_nolock() {
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];
    RefcountMap::iterator it = table.refcnts.find(this); // key 拿值
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

__unsafe_unretained 不安全如何體現?上 Demo

__weak Person *p2;
__unsafe_unretained Person *p3;
{
    Person *p = [[Person alloc] init];
    p2 = p;
}
NSLog(@"%@", p2);
2022-04-12 21:39:30.308917+0800 Main[5307:98296] -[Person dealloc]
2022-04-12 21:39:30.309413+0800 Main[5307:98296] (null)

可以看到出了程式碼塊,之後 p2 雖然指向 p,但是 p 沒有強指標指向,所以回收了,此時列印 p2,是 null。

__unsafe_unretained Person *p3;
{
    Person *p = [[Person alloc] init];
    p3 = p;
}
NSLog(@"%@", p3);
2022-04-12 21:40:47.558581+0800 Main[5342:99598] -[Person dealloc]
2022-04-12 21:40:47.559330+0800 Main[5342:99598] <Person: 0x101206130>

當用 __unsafe_unretained 修飾後,雖然釋放了,但是記憶體還沒回收,這時候去使用很容易出錯。

dealloc 是如何工作的?

在 MRC 時代,寫完程式碼都需要顯示在 dealloc 方法中做一些記憶體回收之類的工作。物件析構時將內部物件先 release 掉,非 OC 物件(比如定時器、c 物件、CF 物件等) 也需要回收記憶體,最後呼叫 [super dealloc] 繼續將父類物件做析構。

- (void)dealloc {
    CFRelease(XX);
    self.timer = nil;
    [super dealloc];
}

但在 ARC 時代,dealloc 中一般只需要寫一些非 OC 物件的記憶體釋放工作,比如 CFRelease()

帶來2個問題:

  • 類中的例項變數在哪釋放?
  • 當前類中沒有顯示呼叫 [super dealloc] ,父類的析構如何觸發?

LLVM 文件對 dealloc 的描述

LLVM ARC 文件對 dealloc 描述 如下

A class may provide a method definition for an instance method named dealloc. This method will be called after the final release of the object but before it is deallocated or any of its instance variables are destroyed. The superclass’s implementation of dealloc will be called automatically when the method returns.

The instance variables for an ARC-compiled class will be destroyed at some point after control enters the dealloc method for the root class of the class. The ordering of the destruction of instance variables is unspecified, both within a single class and between subclasses and superclasses.

根據描述可以看到 dealloc 方法在最後一次 release 方法呼叫後觸發,但例項變數(ivars) 還未釋放,父類的 dealloc 方法將會在子類 dealloc 方法返回後自動呼叫。

ARC 模式下,物件的例項變數會在根類 [NSObject dealloc] 中釋放,但是釋放的順序是不一定的。

也就是說會自動呼叫 [super dealloc],那到底如何實現的,探究下。

檢視 objc4 原始碼

- (void)dealloc {
    _objc_rootDealloc(self);
}

void _objc_rootDealloc(id obj) {
    assert(obj);
    obj->rootDealloc();
}

inline void objc_object::rootDealloc() {
    if (isTaggedPointer()) return;  // fixme necessary?
    // fastpath 判斷當前物件是否滿足條件。
    if (fastpath(isa.nonpointer  &&      // nonpointer
                 !isa.weakly_referenced  &&   // 是否有弱引用
                 !isa.has_assoc  &&   // 關聯物件
                 !isa.has_cxx_dtor  &&   // c++ 解構函式
                 !isa.has_sidetable_rc)) // 是否有 SideTable
    {
        assert(!sidetable_present());
        free(this);
    }  else {
        object_dispose((id)this);
    }
}

id object_dispose(id obj){
    if (!obj) return nil;
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

void *objc_destructInstance(id obj) {
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj); // 清除成員變數
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating(); // 將指向當前物件的弱指標置為 nil
    }
    return obj;
}

inline void objc_object::clearDeallocating() {
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }
    assert(!sidetable_present());
}
void objc_object::sidetable_clearDeallocating(){
    SideTable& table = SideTables()[this];
    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;

    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }

    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }

    weak_entry_remove(weak_table, entry);
}

可以清楚看到在 objc_destructInstance 方法中呼叫了3個核心方法

  • object_cxxDestruct(obj): 清除成員變數
  • object_remove_assocations(obj):去除該物件相關的關聯屬性(Category 新增的)
  • obj->clearDeallocating():清空引用技術表和弱引用表,將 weak 引用設定為 nil

繼續看看 object_cxxDestruct 方法內部細節。

神秘的 cxx_destruct

object_cxxDestruct 方法最終會呼叫到 object_cxxDestructFromClass

void object_cxxDestruct(id obj) {
    if (_objc_isTaggedPointerOrNil(obj)) return;
    object_cxxDestructFromClass(obj, obj->ISA());
}

static void object_cxxDestructFromClass(id obj, Class cls) {
    void (*dtor)(id);
    // Call cls's dtor first, then superclasses's dtors.
    for ( ; cls; cls = cls->getSuperclass()) {
        if (!cls->hasCxxDtor()) return; 
        dtor = (void(*)(id))
            lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
        // 呼叫
        if (dtor != (void(*)(id))_objc_msgForward_impcache) {
            if (PrintCxxCtors) {
                _objc_inform("CXX: calling C++ destructors for class %s", 
                             cls->nameForLogging());
            }
            (*dtor)(obj);
        }
    }
}

做的事情就是遍歷,不斷尋找父類中 SEL_cxx_destruct這個 selector,找到函式實現並呼叫。

void sel_init(size_t selrefCount){
#if SUPPORT_PREOPT
    if (PrintPreopt) {
        _objc_inform("PREOPTIMIZATION: using dyld selector opt");
    }
#endif
  namedSelectors.init((unsigned)selrefCount);
    // Register selectors used by libobjc
    mutex_locker_t lock(selLock)
    SEL_cxx_construct = sel_registerNameNoLock(".cxx_construct", NO);
    SEL_cxx_destruct = sel_registerNameNoLock(".cxx_destruct", NO);
}

繼續翻閱原始碼發現 SEL_cxx_destruct 其實就是 .cxx_destruct。在 《Effective Objective-C 2.0》中說明:

When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it.

也就是說,當編譯器看到 C++ 物件的時候,它將會生成 .cxx_destruct 析構方法,但是 ARC 借用這個方法,並在其中插入了程式碼以實現自動記憶體釋放的功能。

探究啥時候生成 .cxx_destruct 方法

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end
// 
- (void)viewDidLoad {
    [super viewDidLoad];
    {
        NSLog(@"comes");
        Person *p = [[Person alloc] init];
        p.name = @"杭城小劉";
        NSLog(@"gone");
    }
}

在 gone 處加斷點,利用 runtime 檢視類中的方法資訊

發現存在 .cxx_destruct 方法。

我們一開要研究的是 ivars 啥時候釋放,所以控制變數,將屬性改為成員物件

@interface Person : NSObject
{
    @public
    NSString *name;
}
@end

{
    NSLog(@"comes");
    Person *p = [[Person alloc] init];
    p->name = @"杭城小劉";
    NSLog(@"gone");
}

也有 .cxx_destruct 方法

將成員變數換為基本資料型別

@interface Person : NSObject
{
    @public
    int age;
}
@end

Tips:@property 會自動生成成員變數,另外類後面加 {} 在內部也可以加成員變數,假如成員變數是物件型別,比如 NSString,則叫例項變數。

得出結論:

  • 只有 ARC 模式下才有 .cxx_destruct 方法
  • 類擁有例項變數的時候({} 或者 @property) 才有 .cxx_destruct,父類成員物件的例項變數不會讓子類擁有該方法

使用 watchpoint 觀察記憶體釋放時機

在 gone 的地方加斷點,輸入 watchpoint set variable p->_name,則會將 _name 例項變數加入 watchpoint,當變數被修改時會觸發斷點,可以看出從某個值變為 0x0,也就是 nil。此時邊上呼叫堆疊顯示在 objc_storestrong 方法中,被設定為 nil.

深入 .cxx_destruct

簡單梳理下,在 ARC 模式下,類擁有例項變數的時候會在 .cxx_destruct 方法內呼叫 objc_storeStrong 去釋放的記憶體。

我們也知道 .cxx_destruct 是編譯器生成的程式碼。去查詢資料 .cxx_destruct site:clang.llvm.org

在 clang 的 doxygen 文件中 CodeGenModule 模組原始碼發現了相關邏輯。在 5907 行程式碼

void CodeGenModule::EmitObjCIvarInitializations(ObjCImplementationDecl *D) {
  // We might need a .cxx_destruct even if we don't have any ivar initializers.
  if (needsDestructMethod(D)) {
    IdentifierInfo *II = &getContext().Idents.get(".cxx_destruct");
    Selector cxxSelector = getContext().Selectors.getSelector(0, &II);
    ObjCMethodDecl *DTORMethod = ObjCMethodDecl::Create(
        getContext(), D->getLocation(), D->getLocation(), cxxSelector,
        getContext().VoidTy, nullptr, D,
        /*isInstance=*/true, /*isVariadic=*/false,
        /*isPropertyAccessor=*/true, /*isSynthesizedAccessorStub=*/false,
        /*isImplicitlyDeclared=*/true,
        /*isDefined=*/false, ObjCMethodDecl::Required);
    D->addInstanceMethod(DTORMethod);
    CodeGenFunction(*this).GenerateObjCCtorDtorMethod(D, DTORMethod, false);
    D->setHasDestructors(true);
  }

  // If the implementation doesn't have any ivar initializers, we don't need
  // a .cxx_construct.
  if (D->getNumIvarInitializers() == 0 ||
      AllTrivialInitializers(*this, D))
    return;

  IdentifierInfo *II = &getContext().Idents.get(".cxx_construct");
  Selector cxxSelector = getContext().Selectors.getSelector(0, &II);
  // The constructor returns 'self'.
  ObjCMethodDecl *CTORMethod = ObjCMethodDecl::Create(
      getContext(), D->getLocation(), D->getLocation(), cxxSelector,
      getContext().getObjCIdType(), nullptr, D, /*isInstance=*/true,
      /*isVariadic=*/false,
      /*isPropertyAccessor=*/true, /*isSynthesizedAccessorStub=*/false,
      /*isImplicitlyDeclared=*/true,
      /*isDefined=*/false, ObjCMethodDecl::Required);
  D->addInstanceMethod(CTORMethod);
  CodeGenFunction(*this).GenerateObjCCtorDtorMethod(D, CTORMethod, true);
  D->setHasNonZeroConstructors(true);
}

原始碼大概做的事情就是:獲取 .cxx_destructor 的 selector,建立 Method,然後將新建立的 Method 插入到 class 方法列表中。呼叫 GenerateObjCCtorDtorMethod 方法,才建立這個方法的實現。檢視 GenerateObjCCtorDtorMethod 的實現。在 https://clang.llvm.org/doxyge... 的1626行處。

static void emitCXXDestructMethod(CodeGenFunction &CGF,
                                   ObjCImplementationDecl *impl) {
   CodeGenFunction::RunCleanupsScope scope(CGF);

   llvm::Value *self = CGF.LoadObjCSelf();

   const ObjCInterfaceDecl *iface = impl->getClassInterface();
   for (const ObjCIvarDecl *ivar = iface->all_declared_ivar_begin();
        ivar; ivar = ivar->getNextIvar()) {
     QualType type = ivar->getType();

     // Check whether the ivar is a destructible type.
     QualType::DestructionKind dtorKind = type.isDestructedType();
     if (!dtorKind) continue;

     CodeGenFunction::Destroyer *destroyer = nullptr;

     // Use a call to objc_storeStrong to destroy strong ivars, for the
     // general benefit of the tools.
     if (dtorKind == QualType::DK_objc_strong_lifetime) {
       destroyer = destroyARCStrongWithStore;

     // Otherwise use the default for the destruction kind.
     } else {
       destroyer = CGF.getDestroyer(dtorKind);
     }

     CleanupKind cleanupKind = CGF.getCleanupKind(dtorKind);

     CGF.EHStack.pushCleanup<DestroyIvar>(cleanupKind, self, ivar, destroyer,
                                          cleanupKind & EHCleanup);
   }

   assert(scope.requiresCleanups() && "nothing to do in .cxx_destruct?");
 }

可以看到:遍歷了當前物件的所有例項變數,呼叫 objc_storeStrong,從 clang 文件上可以看出

id objc_storeStrong(id *object, id value) {
  value = [value retain];
  id oldValue = *object;
  *object = value;
  [oldValue release];
  return value;
}

.cxx_destruct 方法內部會對所有的例項變數呼叫 objc_storeStrong(&ivar, null) ,例項變數就會 release 。

自動呼叫 [super dealloc] 的原理

同理,CodeGen 也會做自動呼叫 [super dealloc] 的事情。https://clang.llvm.org/doxyge...,第751行 StartObjCMethod 方法。

  751 void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
  752                                       const ObjCContainerDecl *CD) {
  // ...  
  789   // In ARC, certain methods get an extra cleanup.
  790   if (CGM.getLangOpts().ObjCAutoRefCount &&
  791       OMD->isInstanceMethod() &&
  792       OMD->getSelector().isUnarySelector()) {
  793     const IdentifierInfo *ident =
  794       OMD->getSelector().getIdentifierInfoForSlot(0);
  795     if (ident->isStr("dealloc"))
  796       EHStack.pushCleanup<FinishARCDealloc>(getARCCleanupKind());
  797   }
  798 }

可以看到在呼叫到 dealloc 方法時,插入了程式碼,實現如下

struct FinishARCDealloc : EHScopeStack::Cleanup {
   void Emit(CodeGenFunction &CGF, Flags flags) override {
     const ObjCMethodDecl *method = cast<ObjCMethodDecl>(CGF.CurCodeDecl);

     const ObjCImplDecl *impl = cast<ObjCImplDecl>(method->getDeclContext());
     const ObjCInterfaceDecl *iface = impl->getClassInterface();
     if (!iface->getSuperClass()) return;

     bool isCategory = isa<ObjCCategoryImplDecl>(impl);

     // Call [super dealloc] if we have a superclass.
     llvm::Value *self = CGF.LoadObjCSelf();

     CallArgList args;
     CGF.CGM.getObjCRuntime().GenerateMessageSendSuper(CGF, ReturnValueSlot(),
                                                       CGF.getContext().VoidTy,
                                                       method->getSelector(),
                                                       iface,
                                                       isCategory,
                                                       self,
                                                       /*is class msg*/ false,
                                                       args,
                                                       method);
   }
};

程式碼大概就是向父類轉發 dealloc 的呼叫實現,內部自動呼叫 [super dealloc] 方法。

總結下:

  • ARC 模式下,例項變數由編譯器插入 .cxx_destruct 方法自動釋放
  • ARC 模式下 [super dealloc] 由 llvm 編譯器自動插入(CodeGen)

AutoreleasePool 底層原理探索

單 AutoreleasePool 的 case

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[[Person alloc] init] autorelease];
    }
    return 0;
}

clang 轉為 c++ xcrun -sdk iphonesimulator clang -rewrite-objc main.m

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ {  __AtAutoreleasePool __autoreleasepool; 
        Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
    }
    return 0;
}

下面的程式碼其實就是 objc_msgSend,有效程式碼是 __AtAutoreleasePool __autoreleasepool;

繼續查詢

struct __AtAutoreleasePool {
    __AtAutoreleasePool() {
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    ~__AtAutoreleasePool() {
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    void * atautoreleasepoolobj;
};

OC 物件本質就是結構體

  • __AtAutoreleasePool 結構體中 __AtAutoreleasePool 是構造方法,在建立結構體的時候呼叫
  • ~__AtAutoreleasePool 是解構函式,在結構體銷燬的時候呼叫

main 內的程式碼作用域,離開代表銷燬。所以上面程式碼等價於

atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *p = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);

利用關鍵函式繼續檢視 objc4 原始碼

void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}

自動釋放池的主要實現依靠2個物件:__AtAutoreleasePoolAutoreleasePoolPage

objc_autoreleasePoolPush、objc_autoreleasePoolPop 底層都是呼叫了 AutoreleasePoolPage 物件來管理的。

檢視原始碼

class AutoreleasePoolPage {
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}
  • 每個 AutoreleasePoolPage 物件佔用 4096 位元組記憶體,除了用來存放它內部的成員變數,剩下的空間用來存放 autorelease 物件的地址
  • 所有的 AutoreleasePoolPage 物件透過雙向連結串列的形式連線在一起。child 指向下一個物件,parent 指向上一個物件

id * begin() {
    return (id *) ((uint8_t *)this+sizeof(*this));
}

id * end() {
    return (id *) ((uint8_t *)this+SIZE);
}

其中 begin 方法返回 autoreleasePoolPage 物件中開始儲存 autorelease 物件的開始地址

end 方法返回 autoreleasePoolPage 物件中結束儲存 autorelease 物件的開始地址

呼叫 AutoreleasePoolPage::push 方法會將一個 POOL_BOUNDARY 入棧,並且返回其存放的記憶體地址

呼叫 AutoreleasePoolPage::pop 方法時傳入一個 POOL_BOUNDARY 的記憶體地址,系統會從最後一個入棧的物件開始傳送 release消 息,直到遇到這個 POOL_BOUNDARY

id *next 指向了下一個能存放 autorelease 物件地址的區域

static inline void *push() {
    id *dest;
    if (DebugPoolAllocation) {
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}

多 AutoreleasePool 的 case

來個騷一些的例子

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p1 = [[[Person alloc] init] autorelease];
        Person *p2 = [[[Person alloc] init] autorelease];
        @autoreleasepool {
            Person *p3 = [[[Person alloc] init] autorelease];
            @autoreleasepool {
                Person *p4 = [[[Person alloc] init] autorelease];
            }
        }
    }
    return 0;
}

main 方法內部3個 autoreleasepool 底層怎麼樣工作的?

3個@auto releasepool, 系統遇到第一個的時候底層就是初始化一個結構體 __AtAutoreleasePool,結構體構造方法內部呼叫 AutoreleasePoolPage::push 方法,系統給 AutoreleasePoolPage 真正儲存 autorelease 物件的地方儲存進一個 POOL_BOUNDARY 物件,然後儲存 P1、P2 物件地址,遇到第二個則繼續初始化結構體,呼叫 push 方法,儲存一個 POOL_BOUNDARY 物件,繼續儲存 P3,遇到第三個則繼續初始化結構體,呼叫 push 方法,儲存一個 POOL_BOUNDARY 物件,繼續儲存 P4。

當結束第三個大括號的時候,第三個結構體物件,呼叫解構函式,內部呼叫 AutoreleasePoolPage::pop 方法,會從最後一個入棧的物件開始傳送 release 訊息,直到遇到 POOL_BOUNDARY 物件。

緊接著第二個大括號結束,第二個結構體物件解構函式執行,內部呼叫 AutoreleasePoolPage::pop 方法,會從最後一個入棧的物件開始傳送 release 訊息,直到遇到 POOL_BOUNDARY 物件。

所以,巢狀的AutoreleasePool就非常簡單了,pop的時候總會釋放到上次push的位置為止,多層的pool就是多個哨兵物件而已,就像剝洋蔥一樣,每次一層,互不影響

第一個同理。

小竅門,對於上述原理的分析可以用原始碼中看到的 AutoreleasePoolPage 物件的 printAll 方法。

static void printAll() {        
    _objc_inform("##############");
    _objc_inform("AUTORELEASE POOLS for thread %p", pthread_self());

    AutoreleasePoolPage *page;
    ptrdiff_t objects = 0;
    for (page = coldPage(); page; page = page->child) {
        objects += page->next - page->begin();
    }
    _objc_inform("%llu releases pending.", (unsigned long long)objects);

    if (haveEmptyPoolPlaceholder()) {
        _objc_inform("[%p]  ................  PAGE (placeholder)", 
                        EMPTY_POOL_PLACEHOLDER);
        _objc_inform("[%p]  ################  POOL (placeholder)", 
                        EMPTY_POOL_PLACEHOLDER);
    }
    else {
        for (page = coldPage(); page; page = page->child) {
            page->print();
        }
    }
    _objc_inform("##############");
}


void _objc_autoreleasePoolPrint(void) {
    AutoreleasePoolPage::printAll();
}

查了下 printAll 函式的使用方,就只有 _objc_autoreleasePoolPrint 函式。且可以看到在 objc4 objc-internal.h 標頭檔案中有將該函式 export 出去,也就是可以在外部連結該符號。

OBJC_EXPORT void _objc_autoreleasePoolPrint(void) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);

所以我們在測試 Demo 中將 _objc_autoreleasePoolPrint 函式宣告下。在列印下

extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p1 = [[[Person alloc] init] autorelease];
        Person *p2 = [[[Person alloc] init] autorelease];
        @autoreleasepool {
            Person *p3 = [[[Person alloc] init] autorelease];
            @autoreleasepool {
                Person *p4 = [[[Person alloc] init] autorelease];
                _objc_autoreleasePoolPrint();
            }
        }
    }
    return 0;
}
objc[23132]: ##############
objc[23132]: AUTORELEASE POOLS for thread 0x100094600
objc[23132]: 7 releases pending.
objc[23132]: [0x10080a000]  ................  PAGE  (hot) (cold)
objc[23132]: [0x10080a038]  ################  POOL 0x10080a038
objc[23132]: [0x10080a040]       0x10075f060  Person
objc[23132]: [0x10080a048]       0x10075f0c0  Person
objc[23132]: [0x10080a050]  ################  POOL 0x10080a050
objc[23132]: [0x10080a058]       0x10075f0e0  Person
objc[23132]: [0x10080a060]  ################  POOL 0x10080a060
objc[23132]: [0x10080a068]       0x10075f100  Person
objc[23132]: ##############

可以看到列印結果和上面的分析是一致的(和上面的圖片對比看看)

再來個 Demo,驗證下 AutoreleasePoolPage 一頁滿情況

extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p1 = [[[Person alloc] init] autorelease];
        Person *p2 = [[[Person alloc] init] autorelease];
        @autoreleasepool {
            for (NSInteger index = 0; index<600; index++) {
                Person *p3 = [[[Person alloc] init] autorelease];
            }
            @autoreleasepool {
                Person *p4 = [[[Person alloc] init] autorelease];
                _objc_autoreleasePoolPrint();
            }
        }
    }
    return 0;
}

objc[23504]: ##############
objc[23504]: AUTORELEASE POOLS for thread 0x100094600
objc[23504]: 606 releases pending.
objc[23504]: [0x10080d000]  ................  PAGE (full)  (cold)
objc[23504]: [0x10080d038]  ################  POOL 0x10080d038
objc[23504]: [0x10080d040]       0x1007092f0  Person
objc[23504]: [0x10080d048]       0x100709350  Person
objc[23504]: [0x10080d050]  ################  POOL 0x10080d050
objc[23504]: [0x10080d058]       0x100753250  Person
objc[23504]: [0x10080d060]       0x100753270  Person
objc[23504]: [0x10080d068]       0x100753290  Person
objc[23504]: [0x10080d070]       0x1007532b0  Person
objc[23504]: [0x10080d078]       0x1007532d0  Person
objc[23504]: [0x10080d080]       0x1007532f0  Person
objc[23504]: [0x10080d088]       0x100753310  Person
objc[23504]: [0x10080d090]       0x100753330  Person
objc[23504]: [0x10080d098]       0x100753680  Person
objc[23504]: [0x10080d0a0]       0x1007536a0  Person
objc[23504]: [0x10080d0a8]       0x1007536c0  Person
objc[23504]: [0x10080d0b0]       0x1007536e0  Person
objc[23504]: [0x10080d0b8]       0x100753700  Person
objc[23504]: [0x10080d0c0]       0x100753720  Person
objc[23504]: [0x10080d0c8]       0x100753740  Person
objc[23504]: [0x10080d0d0]       0x100753760  Person
objc[23504]: [0x10080d0d8]       0x100753780  Person
objc[23504]: [0x10080d0e0]       0x1007537a0  Person
objc[23504]: [0x10080d0e8]       0x1007537c0  Person
objc[23504]: [0x10080d0f0]       0x1007537e0  Person
objc[23504]: [0x10080d0f8]       0x100753800  Person
objc[23504]: [0x10080d100]       0x100753820  Person
objc[23504]: [0x10080d108]       0x100753840  Person
objc[23504]: [0x10080d110]       0x100753860  Person
objc[23504]: [0x10080d118]       0x100753880  Person
objc[23504]: [0x10080d120]       0x1007538a0  Person
objc[23504]: [0x10080d128]       0x1007538c0  Person
objc[23504]: [0x10080d130]       0x1007538e0  Person
objc[23504]: [0x10080d138]       0x100753900  Person
objc[23504]: [0x10080d140]       0x100753920  Person
objc[23504]: [0x10080d148]       0x100753940  Person
objc[23504]: [0x10080d150]       0x100753960  Person
objc[23504]: [0x10080d158]       0x100753980  Person
objc[23504]: [0x10080d160]       0x1007539a0  Person
objc[23504]: [0x10080d168]       0x1007539c0  Person
objc[23504]: [0x10080d170]       0x1007539e0  Person
objc[23504]: [0x10080d178]       0x100753a00  Person
objc[23504]: [0x10080d180]       0x100753a20  Person
objc[23504]: [0x10080d188]       0x100753a40  Person
objc[23504]: [0x10080d190]       0x100753a60  Person
objc[23504]: [0x10080d198]       0x100753a80  Person
objc[23504]: [0x10080d1a0]       0x100753aa0  Person
objc[23504]: [0x10080d1a8]       0x100753ac0  Person
objc[23504]: [0x10080d1b0]       0x100753ae0  Person
objc[23504]: [0x10080d1b8]       0x100753b00  Person
objc[23504]: [0x10080d1c0]       0x100753b20  Person
objc[23504]: [0x10080d1c8]       0x100753b40  Person
objc[23504]: [0x10080d1d0]       0x100753b60  Person
objc[23504]: [0x10080d1d8]       0x100753b80  Person
objc[23504]: [0x10080d1e0]       0x100753ba0  Person
objc[23504]: [0x10080d1e8]       0x100753bc0  Person
objc[23504]: [0x10080d1f0]       0x100753be0  Person
objc[23504]: [0x10080d1f8]       0x100753c00  Person
objc[23504]: [0x10080d200]       0x100753c20  Person
objc[23504]: [0x10080d208]       0x100753c40  Person
objc[23504]: [0x10080d210]       0x100753c60  Person
objc[23504]: [0x10080d218]       0x100753c80  Person
objc[23504]: [0x10080d220]       0x100753ca0  Person
objc[23504]: [0x10080d228]       0x100753cc0  Person
objc[23504]: [0x10080d230]       0x100753ce0  Person
objc[23504]: [0x10080d238]       0x100753d00  Person
objc[23504]: [0x10080d240]       0x100753d20  Person
objc[23504]: [0x10080d248]       0x100753d40  Person
objc[23504]: [0x10080d250]       0x100753d60  Person
objc[23504]: [0x10080d258]       0x100753d80  Person
objc[23504]: [0x10080d260]       0x100753da0  Person
objc[23504]: [0x10080d268]       0x100753dc0  Person
objc[23504]: [0x10080d270]       0x100753de0  Person
objc[23504]: [0x10080d278]       0x100753e00  Person
objc[23504]: [0x10080d280]       0x100753e20  Person
objc[23504]: [0x10080d288]       0x100753e40  Person
objc[23504]: [0x10080d290]       0x100753e60  Person
objc[23504]: [0x10080d298]       0x100753e80  Person
objc[23504]: [0x10080d2a0]       0x100753ea0  Person
objc[23504]: [0x10080d2a8]       0x100753ec0  Person
objc[23504]: [0x10080d2b0]       0x100753ee0  Person
objc[23504]: [0x10080d2b8]       0x100753f00  Person
objc[23504]: [0x10080d2c0]       0x100753f20  Person
objc[23504]: [0x10080d2c8]       0x100753f40  Person
objc[23504]: [0x10080d2d0]       0x100753f60  Person
objc[23504]: [0x10080d2d8]       0x100753f80  Person
objc[23504]: [0x10080d2e0]       0x100753fa0  Person
objc[23504]: [0x10080d2e8]       0x100753fc0  Person
objc[23504]: [0x10080d2f0]       0x100753fe0  Person
objc[23504]: [0x10080d2f8]       0x100754000  Person
objc[23504]: [0x10080d300]       0x100754020  Person
objc[23504]: [0x10080d308]       0x100754040  Person
objc[23504]: [0x10080d310]       0x100754060  Person
objc[23504]: [0x10080d318]       0x100754080  Person
objc[23504]: [0x10080d320]       0x1007540a0  Person
objc[23504]: [0x10080d328]       0x1007540c0  Person
objc[23504]: [0x10080d330]       0x1007540e0  Person
objc[23504]: [0x10080d338]       0x100754100  Person
objc[23504]: [0x10080d340]       0x100754120  Person
objc[23504]: [0x10080d348]       0x100754140  Person
objc[23504]: [0x10080d350]       0x100754160  Person
objc[23504]: [0x10080d358]       0x100754180  Person
objc[23504]: [0x10080d360]       0x1007541a0  Person
objc[23504]: [0x10080d368]       0x1007541c0  Person
objc[23504]: [0x10080d370]       0x1007541e0  Person
objc[23504]: [0x10080d378]       0x100754200  Person
objc[23504]: [0x10080d380]       0x100754220  Person
objc[23504]: [0x10080d388]       0x100754240  Person
objc[23504]: [0x10080d390]       0x100754260  Person
objc[23504]: [0x10080d398]       0x100754280  Person
objc[23504]: [0x10080d3a0]       0x1007542a0  Person
objc[23504]: [0x10080d3a8]       0x1007542c0  Person
objc[23504]: [0x10080d3b0]       0x1007542e0  Person
objc[23504]: [0x10080d3b8]       0x100754300  Person
objc[23504]: [0x10080d3c0]       0x100754320  Person
objc[23504]: [0x10080d3c8]       0x100754340  Person
objc[23504]: [0x10080d3d0]       0x100754360  Person
objc[23504]: [0x10080d3d8]       0x100754380  Person
objc[23504]: [0x10080d3e0]       0x1007543a0  Person
objc[23504]: [0x10080d3e8]       0x1007543c0  Person
objc[23504]: [0x10080d3f0]       0x1007543e0  Person
objc[23504]: [0x10080d3f8]       0x100754400  Person
objc[23504]: [0x10080d400]       0x100754420  Person
objc[23504]: [0x10080d408]       0x100754440  Person
objc[23504]: [0x10080d410]       0x100754460  Person
objc[23504]: [0x10080d418]       0x100754480  Person
objc[23504]: [0x10080d420]       0x1007544a0  Person
objc[23504]: [0x10080d428]       0x1007544c0  Person
objc[23504]: [0x10080d430]       0x1007544e0  Person
objc[23504]: [0x10080d438]       0x100754500  Person
objc[23504]: [0x10080d440]       0x100754520  Person
objc[23504]: [0x10080d448]       0x100754540  Person
objc[23504]: [0x10080d450]       0x100754560  Person
objc[23504]: [0x10080d458]       0x100754580  Person
objc[23504]: [0x10080d460]       0x1007545a0  Person
objc[23504]: [0x10080d468]       0x1007545c0  Person
objc[23504]: [0x10080d470]       0x1007545e0  Person
objc[23504]: [0x10080d478]       0x100754600  Person
objc[23504]: [0x10080d480]       0x100754620  Person
objc[23504]: [0x10080d488]       0x100754640  Person
objc[23504]: [0x10080d490]       0x100754660  Person
objc[23504]: [0x10080d498]       0x100754680  Person
objc[23504]: [0x10080d4a0]       0x1007546a0  Person
objc[23504]: [0x10080d4a8]       0x1007546c0  Person
objc[23504]: [0x10080d4b0]       0x1007546e0  Person
objc[23504]: [0x10080d4b8]       0x100754700  Person
objc[23504]: [0x10080d4c0]       0x100754720  Person
objc[23504]: [0x10080d4c8]       0x100754740  Person
objc[23504]: [0x10080d4d0]       0x100754760  Person
objc[23504]: [0x10080d4d8]       0x100754780  Person
objc[23504]: [0x10080d4e0]       0x1007547a0  Person
objc[23504]: [0x10080d4e8]       0x1007547c0  Person
objc[23504]: [0x10080d4f0]       0x1007547e0  Person
objc[23504]: [0x10080d4f8]       0x100754800  Person
objc[23504]: [0x10080d500]       0x100754820  Person
objc[23504]: [0x10080d508]       0x100754840  Person
objc[23504]: [0x10080d510]       0x100754860  Person
objc[23504]: [0x10080d518]       0x100754880  Person
objc[23504]: [0x10080d520]       0x1007548a0  Person
objc[23504]: [0x10080d528]       0x1007548c0  Person
objc[23504]: [0x10080d530]       0x1007548e0  Person
objc[23504]: [0x10080d538]       0x100754900  Person
objc[23504]: [0x10080d540]       0x100754920  Person
objc[23504]: [0x10080d548]       0x100754940  Person
objc[23504]: [0x10080d550]       0x100754960  Person
objc[23504]: [0x10080d558]       0x100754980  Person
objc[23504]: [0x10080d560]       0x1007549a0  Person
objc[23504]: [0x10080d568]       0x1007549c0  Person
objc[23504]: [0x10080d570]       0x1007549e0  Person
objc[23504]: [0x10080d578]       0x100754a00  Person
objc[23504]: [0x10080d580]       0x100754a20  Person
objc[23504]: [0x10080d588]       0x100754a40  Person
objc[23504]: [0x10080d590]       0x100754a60  Person
objc[23504]: [0x10080d598]       0x100754a80  Person
objc[23504]: [0x10080d5a0]       0x100754aa0  Person
objc[23504]: [0x10080d5a8]       0x100754ac0  Person
objc[23504]: [0x10080d5b0]       0x100754ae0  Person
objc[23504]: [0x10080d5b8]       0x100754b00  Person
objc[23504]: [0x10080d5c0]       0x100754b20  Person
objc[23504]: [0x10080d5c8]       0x100754b40  Person
objc[23504]: [0x10080d5d0]       0x100754b60  Person
objc[23504]: [0x10080d5d8]       0x100754b80  Person
objc[23504]: [0x10080d5e0]       0x100754ba0  Person
objc[23504]: [0x10080d5e8]       0x100754bc0  Person
objc[23504]: [0x10080d5f0]       0x100754be0  Person
objc[23504]: [0x10080d5f8]       0x100754c00  Person
objc[23504]: [0x10080d600]       0x100754c20  Person
objc[23504]: [0x10080d608]       0x100754c40  Person
objc[23504]: [0x10080d610]       0x100754c60  Person
objc[23504]: [0x10080d618]       0x100754c80  Person
objc[23504]: [0x10080d620]       0x100754ca0  Person
objc[23504]: [0x10080d628]       0x100754cc0  Person
objc[23504]: [0x10080d630]       0x100754ce0  Person
objc[23504]: [0x10080d638]       0x100754d00  Person
objc[23504]: [0x10080d640]       0x100754d20  Person
objc[23504]: [0x10080d648]       0x100754d40  Person
objc[23504]: [0x10080d650]       0x100754d60  Person
objc[23504]: [0x10080d658]       0x100754d80  Person
objc[23504]: [0x10080d660]       0x100754da0  Person
objc[23504]: [0x10080d668]       0x100754dc0  Person
objc[23504]: [0x10080d670]       0x100754de0  Person
objc[23504]: [0x10080d678]       0x100754e00  Person
objc[23504]: [0x10080d680]       0x10074fa70  Person
objc[23504]: [0x10080d688]       0x10074fa90  Person
objc[23504]: [0x10080d690]       0x10074fab0  Person
objc[23504]: [0x10080d698]       0x10074fad0  Person
objc[23504]: [0x10080d6a0]       0x10074faf0  Person
objc[23504]: [0x10080d6a8]       0x10074fb10  Person
objc[23504]: [0x10080d6b0]       0x10074fb30  Person
objc[23504]: [0x10080d6b8]       0x10074fb50  Person
objc[23504]: [0x10080d6c0]       0x10074fb70  Person
objc[23504]: [0x10080d6c8]       0x10074fb90  Person
objc[23504]: [0x10080d6d0]       0x10074fbb0  Person
objc[23504]: [0x10080d6d8]       0x10074fbd0  Person
objc[23504]: [0x10080d6e0]       0x10074fbf0  Person
objc[23504]: [0x10080d6e8]       0x10074fc10  Person
objc[23504]: [0x10080d6f0]       0x10074fc30  Person
objc[23504]: [0x10080d6f8]       0x10074fc50  Person
objc[23504]: [0x10080d700]       0x10074fc70  Person
objc[23504]: [0x10080d708]       0x10074fc90  Person
objc[23504]: [0x10080d710]       0x10074fcb0  Person
objc[23504]: [0x10080d718]       0x10074fcd0  Person
objc[23504]: [0x10080d720]       0x10074fcf0  Person
objc[23504]: [0x10080d728]       0x10074fd10  Person
objc[23504]: [0x10080d730]       0x10074fd30  Person
objc[23504]: [0x10080d738]       0x10074fd50  Person
objc[23504]: [0x10080d740]       0x10074fd70  Person
objc[23504]: [0x10080d748]       0x10074fd90  Person
objc[23504]: [0x10080d750]       0x10074fdb0  Person
objc[23504]: [0x10080d758]       0x10074fdd0  Person
objc[23504]: [0x10080d760]       0x10074fdf0  Person
objc[23504]: [0x10080d768]       0x10074fe10  Person
objc[23504]: [0x10080d770]       0x10074fe30  Person
objc[23504]: [0x10080d778]       0x10074fe50  Person
objc[23504]: [0x10080d780]       0x10074fe70  Person
objc[23504]: [0x10080d788]       0x10074fe90  Person
objc[23504]: [0x10080d790]       0x10074feb0  Person
objc[23504]: [0x10080d798]       0x10074fed0  Person
objc[23504]: [0x10080d7a0]       0x10074fef0  Person
objc[23504]: [0x10080d7a8]       0x10074ff10  Person
objc[23504]: [0x10080d7b0]       0x10074ff30  Person
objc[23504]: [0x10080d7b8]       0x10074ff50  Person
objc[23504]: [0x10080d7c0]       0x10074ff70  Person
objc[23504]: [0x10080d7c8]       0x10074ff90  Person
objc[23504]: [0x10080d7d0]       0x10074ffb0  Person
objc[23504]: [0x10080d7d8]       0x10074ffd0  Person
objc[23504]: [0x10080d7e0]       0x10074fff0  Person
objc[23504]: [0x10080d7e8]       0x100750010  Person
objc[23504]: [0x10080d7f0]       0x100750030  Person
objc[23504]: [0x10080d7f8]       0x100750050  Person
objc[23504]: [0x10080d800]       0x100750070  Person
objc[23504]: [0x10080d808]       0x100750090  Person
objc[23504]: [0x10080d810]       0x1007500b0  Person
objc[23504]: [0x10080d818]       0x1007500d0  Person
objc[23504]: [0x10080d820]       0x1007500f0  Person
objc[23504]: [0x10080d828]       0x100750110  Person
objc[23504]: [0x10080d830]       0x100750130  Person
objc[23504]: [0x10080d838]       0x100750150  Person
objc[23504]: [0x10080d840]       0x100750170  Person
objc[23504]: [0x10080d848]       0x100750190  Person
objc[23504]: [0x10080d850]       0x1007501b0  Person
objc[23504]: [0x10080d858]       0x1007501d0  Person
objc[23504]: [0x10080d860]       0x1007501f0  Person
objc[23504]: [0x10080d868]       0x100750210  Person
objc[23504]: [0x10080d870]       0x100750230  Person
objc[23504]: [0x10080d878]       0x100750250  Person
objc[23504]: [0x10080d880]       0x100750270  Person
objc[23504]: [0x10080d888]       0x100750290  Person
objc[23504]: [0x10080d890]       0x1007502b0  Person
objc[23504]: [0x10080d898]       0x1007502d0  Person
objc[23504]: [0x10080d8a0]       0x1007502f0  Person
objc[23504]: [0x10080d8a8]       0x100750310  Person
objc[23504]: [0x10080d8b0]       0x100750330  Person
objc[23504]: [0x10080d8b8]       0x100750350  Person
objc[23504]: [0x10080d8c0]       0x100750370  Person
objc[23504]: [0x10080d8c8]       0x100750390  Person
objc[23504]: [0x10080d8d0]       0x1007503b0  Person
objc[23504]: [0x10080d8d8]       0x1007503d0  Person
objc[23504]: [0x10080d8e0]       0x1007503f0  Person
objc[23504]: [0x10080d8e8]       0x100750410  Person
objc[23504]: [0x10080d8f0]       0x100750430  Person
objc[23504]: [0x10080d8f8]       0x100750450  Person
objc[23504]: [0x10080d900]       0x100750470  Person
objc[23504]: [0x10080d908]       0x100750490  Person
objc[23504]: [0x10080d910]       0x1007504b0  Person
objc[23504]: [0x10080d918]       0x1007504d0  Person
objc[23504]: [0x10080d920]       0x1007504f0  Person
objc[23504]: [0x10080d928]       0x100750510  Person
objc[23504]: [0x10080d930]       0x100750530  Person
objc[23504]: [0x10080d938]       0x100750550  Person
objc[23504]: [0x10080d940]       0x100750570  Person
objc[23504]: [0x10080d948]       0x100750590  Person
objc[23504]: [0x10080d950]       0x1007505b0  Person
objc[23504]: [0x10080d958]       0x1007505d0  Person
objc[23504]: [0x10080d960]       0x1007505f0  Person
objc[23504]: [0x10080d968]       0x100750610  Person
objc[23504]: [0x10080d970]       0x100750630  Person
objc[23504]: [0x10080d978]       0x100750650  Person
objc[23504]: [0x10080d980]       0x100750670  Person
objc[23504]: [0x10080d988]       0x100750690  Person
objc[23504]: [0x10080d990]       0x1007506b0  Person
objc[23504]: [0x10080d998]       0x1007506d0  Person
objc[23504]: [0x10080d9a0]       0x1007506f0  Person
objc[23504]: [0x10080d9a8]       0x100750710  Person
objc[23504]: [0x10080d9b0]       0x100750730  Person
objc[23504]: [0x10080d9b8]       0x100750750  Person
objc[23504]: [0x10080d9c0]       0x100750770  Person
objc[23504]: [0x10080d9c8]       0x100750790  Person
objc[23504]: [0x10080d9d0]       0x1007507b0  Person
objc[23504]: [0x10080d9d8]       0x1007507d0  Person
objc[23504]: [0x10080d9e0]       0x1007507f0  Person
objc[23504]: [0x10080d9e8]       0x100750810  Person
objc[23504]: [0x10080d9f0]       0x100750830  Person
objc[23504]: [0x10080d9f8]       0x100750850  Person
objc[23504]: [0x10080da00]       0x100750870  Person
objc[23504]: [0x10080da08]       0x100750890  Person
objc[23504]: [0x10080da10]       0x1007508b0  Person
objc[23504]: [0x10080da18]       0x1007508d0  Person
objc[23504]: [0x10080da20]       0x1007508f0  Person
objc[23504]: [0x10080da28]       0x100750910  Person
objc[23504]: [0x10080da30]       0x100750930  Person
objc[23504]: [0x10080da38]       0x100750950  Person
objc[23504]: [0x10080da40]       0x100750970  Person
objc[23504]: [0x10080da48]       0x100750990  Person
objc[23504]: [0x10080da50]       0x1007509b0  Person
objc[23504]: [0x10080da58]       0x1007509d0  Person
objc[23504]: [0x10080da60]       0x1007509f0  Person
objc[23504]: [0x10080da68]       0x100750a10  Person
objc[23504]: [0x10080da70]       0x100750a30  Person
objc[23504]: [0x10080da78]       0x100750a50  Person
objc[23504]: [0x10080da80]       0x100750a70  Person
objc[23504]: [0x10080da88]       0x100750a90  Person
objc[23504]: [0x10080da90]       0x100750ab0  Person
objc[23504]: [0x10080da98]       0x100750ad0  Person
objc[23504]: [0x10080daa0]       0x100750af0  Person
objc[23504]: [0x10080daa8]       0x100750b10  Person
objc[23504]: [0x10080dab0]       0x100750b30  Person
objc[23504]: [0x10080dab8]       0x100750b50  Person
objc[23504]: [0x10080dac0]       0x100750b70  Person
objc[23504]: [0x10080dac8]       0x100750b90  Person
objc[23504]: [0x10080dad0]       0x100750bb0  Person
objc[23504]: [0x10080dad8]       0x100750bd0  Person
objc[23504]: [0x10080dae0]       0x100750bf0  Person
objc[23504]: [0x10080dae8]       0x100750c10  Person
objc[23504]: [0x10080daf0]       0x100750c30  Person
objc[23504]: [0x10080daf8]       0x100750c50  Person
objc[23504]: [0x10080db00]       0x100750c70  Person
objc[23504]: [0x10080db08]       0x100750c90  Person
objc[23504]: [0x10080db10]       0x100750cb0  Person
objc[23504]: [0x10080db18]       0x100750cd0  Person
objc[23504]: [0x10080db20]       0x100750cf0  Person
objc[23504]: [0x10080db28]       0x100750d10  Person
objc[23504]: [0x10080db30]       0x100750d30  Person
objc[23504]: [0x10080db38]       0x100750d50  Person
objc[23504]: [0x10080db40]       0x100750d70  Person
objc[23504]: [0x10080db48]       0x100750d90  Person
objc[23504]: [0x10080db50]       0x100750db0  Person
objc[23504]: [0x10080db58]       0x100750dd0  Person
objc[23504]: [0x10080db60]       0x100750df0  Person
objc[23504]: [0x10080db68]       0x100750e10  Person
objc[23504]: [0x10080db70]       0x100750e30  Person
objc[23504]: [0x10080db78]       0x100750e50  Person
objc[23504]: [0x10080db80]       0x100750e70  Person
objc[23504]: [0x10080db88]       0x100750e90  Person
objc[23504]: [0x10080db90]       0x100750eb0  Person
objc[23504]: [0x10080db98]       0x100750ed0  Person
objc[23504]: [0x10080dba0]       0x100750ef0  Person
objc[23504]: [0x10080dba8]       0x100750f10  Person
objc[23504]: [0x10080dbb0]       0x100750f30  Person
objc[23504]: [0x10080dbb8]       0x100750f50  Person
objc[23504]: [0x10080dbc0]       0x100750f70  Person
objc[23504]: [0x10080dbc8]       0x100750f90  Person
objc[23504]: [0x10080dbd0]       0x100750fb0  Person
objc[23504]: [0x10080dbd8]       0x100750fd0  Person
objc[23504]: [0x10080dbe0]       0x100750ff0  Person
objc[23504]: [0x10080dbe8]       0x100751010  Person
objc[23504]: [0x10080dbf0]       0x100751030  Person
objc[23504]: [0x10080dbf8]       0x100751050  Person
objc[23504]: [0x10080dc00]       0x100751070  Person
objc[23504]: [0x10080dc08]       0x100751090  Person
objc[23504]: [0x10080dc10]       0x1007510b0  Person
objc[23504]: [0x10080dc18]       0x1007510d0  Person
objc[23504]: [0x10080dc20]       0x1007510f0  Person
objc[23504]: [0x10080dc28]       0x100751110  Person
objc[23504]: [0x10080dc30]       0x100751130  Person
objc[23504]: [0x10080dc38]       0x100751150  Person
objc[23504]: [0x10080dc40]       0x100751170  Person
objc[23504]: [0x10080dc48]       0x100751190  Person
objc[23504]: [0x10080dc50]       0x1007511b0  Person
objc[23504]: [0x10080dc58]       0x1007511d0  Person
objc[23504]: [0x10080dc60]       0x1007511f0  Person
objc[23504]: [0x10080dc68]       0x100751210  Person
objc[23504]: [0x10080dc70]       0x100751230  Person
objc[23504]: [0x10080dc78]       0x100751250  Person
objc[23504]: [0x10080dc80]       0x100751270  Person
objc[23504]: [0x10080dc88]       0x100751290  Person
objc[23504]: [0x10080dc90]       0x1007512b0  Person
objc[23504]: [0x10080dc98]       0x1007512d0  Person
objc[23504]: [0x10080dca0]       0x1007512f0  Person
objc[23504]: [0x10080dca8]       0x100751310  Person
objc[23504]: [0x10080dcb0]       0x100751330  Person
objc[23504]: [0x10080dcb8]       0x100751350  Person
objc[23504]: [0x10080dcc0]       0x100751370  Person
objc[23504]: [0x10080dcc8]       0x100751390  Person
objc[23504]: [0x10080dcd0]       0x1007513b0  Person
objc[23504]: [0x10080dcd8]       0x1007513d0  Person
objc[23504]: [0x10080dce0]       0x1007513f0  Person
objc[23504]: [0x10080dce8]       0x100751410  Person
objc[23504]: [0x10080dcf0]       0x100751430  Person
objc[23504]: [0x10080dcf8]       0x100751450  Person
objc[23504]: [0x10080dd00]       0x100751470  Person
objc[23504]: [0x10080dd08]       0x100751490  Person
objc[23504]: [0x10080dd10]       0x1007514b0  Person
objc[23504]: [0x10080dd18]       0x1007514d0  Person
objc[23504]: [0x10080dd20]       0x1007514f0  Person
objc[23504]: [0x10080dd28]       0x100751510  Person
objc[23504]: [0x10080dd30]       0x100751530  Person
objc[23504]: [0x10080dd38]       0x100751550  Person
objc[23504]: [0x10080dd40]       0x100751570  Person
objc[23504]: [0x10080dd48]       0x100751590  Person
objc[23504]: [0x10080dd50]       0x1007515b0  Person
objc[23504]: [0x10080dd58]       0x1007515d0  Person
objc[23504]: [0x10080dd60]       0x1007515f0  Person
objc[23504]: [0x10080dd68]       0x100751610  Person
objc[23504]: [0x10080dd70]       0x100751630  Person
objc[23504]: [0x10080dd78]       0x100751650  Person
objc[23504]: [0x10080dd80]       0x100751670  Person
objc[23504]: [0x10080dd88]       0x100751690  Person
objc[23504]: [0x10080dd90]       0x1007516b0  Person
objc[23504]: [0x10080dd98]       0x1007516d0  Person
objc[23504]: [0x10080dda0]       0x1007516f0  Person
objc[23504]: [0x10080dda8]       0x100751710  Person
objc[23504]: [0x10080ddb0]       0x100751730  Person
objc[23504]: [0x10080ddb8]       0x100751750  Person
objc[23504]: [0x10080ddc0]       0x100751770  Person
objc[23504]: [0x10080ddc8]       0x100751790  Person
objc[23504]: [0x10080ddd0]       0x1007517b0  Person
objc[23504]: [0x10080ddd8]       0x1007517d0  Person
objc[23504]: [0x10080dde0]       0x1007517f0  Person
objc[23504]: [0x10080dde8]       0x100751810  Person
objc[23504]: [0x10080ddf0]       0x100751830  Person
objc[23504]: [0x10080ddf8]       0x100751850  Person
objc[23504]: [0x10080de00]       0x100751870  Person
objc[23504]: [0x10080de08]       0x100751890  Person
objc[23504]: [0x10080de10]       0x1007518b0  Person
objc[23504]: [0x10080de18]       0x1007518d0  Person
objc[23504]: [0x10080de20]       0x1007518f0  Person
objc[23504]: [0x10080de28]       0x100751910  Person
objc[23504]: [0x10080de30]       0x100751930  Person
objc[23504]: [0x10080de38]       0x100751950  Person
objc[23504]: [0x10080de40]       0x100751970  Person
objc[23504]: [0x10080de48]       0x100751990  Person
objc[23504]: [0x10080de50]       0x1007519b0  Person
objc[23504]: [0x10080de58]       0x1007519d0  Person
objc[23504]: [0x10080de60]       0x1007519f0  Person
objc[23504]: [0x10080de68]       0x100751a10  Person
objc[23504]: [0x10080de70]       0x100751a30  Person
objc[23504]: [0x10080de78]       0x100751a50  Person
objc[23504]: [0x10080de80]       0x100751a70  Person
objc[23504]: [0x10080de88]       0x100751a90  Person
objc[23504]: [0x10080de90]       0x100751ab0  Person
objc[23504]: [0x10080de98]       0x100751ad0  Person
objc[23504]: [0x10080dea0]       0x100751af0  Person
objc[23504]: [0x10080dea8]       0x100751b10  Person
objc[23504]: [0x10080deb0]       0x100751b30  Person
objc[23504]: [0x10080deb8]       0x100751b50  Person
objc[23504]: [0x10080dec0]       0x100751b70  Person
objc[23504]: [0x10080dec8]       0x100751b90  Person
objc[23504]: [0x10080ded0]       0x100751bb0  Person
objc[23504]: [0x10080ded8]       0x100751bd0  Person
objc[23504]: [0x10080dee0]       0x100751bf0  Person
objc[23504]: [0x10080dee8]       0x100751c10  Person
objc[23504]: [0x10080def0]       0x100751c30  Person
objc[23504]: [0x10080def8]       0x100751c50  Person
objc[23504]: [0x10080df00]       0x100751c70  Person
objc[23504]: [0x10080df08]       0x100751c90  Person
objc[23504]: [0x10080df10]       0x100751cb0  Person
objc[23504]: [0x10080df18]       0x100751cd0  Person
objc[23504]: [0x10080df20]       0x100751cf0  Person
objc[23504]: [0x10080df28]       0x100751d10  Person
objc[23504]: [0x10080df30]       0x100751d30  Person
objc[23504]: [0x10080df38]       0x100751d50  Person
objc[23504]: [0x10080df40]       0x100751d70  Person
objc[23504]: [0x10080df48]       0x100751d90  Person
objc[23504]: [0x10080df50]       0x100751db0  Person
objc[23504]: [0x10080df58]       0x100751dd0  Person
objc[23504]: [0x10080df60]       0x100751df0  Person
objc[23504]: [0x10080df68]       0x100751e10  Person
objc[23504]: [0x10080df70]       0x100751e30  Person
objc[23504]: [0x10080df78]       0x100751e50  Person
objc[23504]: [0x10080df80]       0x100751e70  Person
objc[23504]: [0x10080df88]       0x100751e90  Person
objc[23504]: [0x10080df90]       0x100751eb0  Person
objc[23504]: [0x10080df98]       0x100751ed0  Person
objc[23504]: [0x10080dfa0]       0x100751ef0  Person
objc[23504]: [0x10080dfa8]       0x100751f10  Person
objc[23504]: [0x10080dfb0]       0x100751f30  Person
objc[23504]: [0x10080dfb8]       0x100751f50  Person
objc[23504]: [0x10080dfc0]       0x100751f70  Person
objc[23504]: [0x10080dfc8]       0x100751f90  Person
objc[23504]: [0x10080dfd0]       0x100751fb0  Person
objc[23504]: [0x10080dfd8]       0x100751fd0  Person
objc[23504]: [0x10080dfe0]       0x100751ff0  Person
objc[23504]: [0x10080dfe8]       0x100752010  Person
objc[23504]: [0x10080dff0]       0x100752030  Person
objc[23504]: [0x10080dff8]       0x100752050  Person
objc[23504]: [0x100817000]  ................  PAGE  (hot) 
objc[23504]: [0x100817038]       0x100752070  Person
objc[23504]: [0x100817040]       0x100752090  Person
objc[23504]: [0x100817048]       0x1007520b0  Person
objc[23504]: [0x100817050]       0x1007520d0  Person
objc[23504]: [0x100817058]       0x1007520f0  Person
objc[23504]: [0x100817060]       0x100752110  Person
objc[23504]: [0x100817068]       0x100752130  Person
objc[23504]: [0x100817070]       0x100752150  Person
objc[23504]: [0x100817078]       0x100752170  Person
objc[23504]: [0x100817080]       0x100752190  Person
objc[23504]: [0x100817088]       0x1007521b0  Person
objc[23504]: [0x100817090]       0x1007521d0  Person
objc[23504]: [0x100817098]       0x1007521f0  Person
objc[23504]: [0x1008170a0]       0x100752210  Person
objc[23504]: [0x1008170a8]       0x100752230  Person
objc[23504]: [0x1008170b0]       0x100752250  Person
objc[23504]: [0x1008170b8]       0x100752270  Person
objc[23504]: [0x1008170c0]       0x100752290  Person
objc[23504]: [0x1008170c8]       0x1007522b0  Person
objc[23504]: [0x1008170d0]       0x1007522d0  Person
objc[23504]: [0x1008170d8]       0x1007522f0  Person
objc[23504]: [0x1008170e0]       0x100752310  Person
objc[23504]: [0x1008170e8]       0x100752330  Person
objc[23504]: [0x1008170f0]       0x100752350  Person
objc[23504]: [0x1008170f8]       0x100752370  Person
objc[23504]: [0x100817100]       0x100752390  Person
objc[23504]: [0x100817108]       0x1007523b0  Person
objc[23504]: [0x100817110]       0x1007523d0  Person
objc[23504]: [0x100817118]       0x1007523f0  Person
objc[23504]: [0x100817120]       0x100752410  Person
objc[23504]: [0x100817128]       0x100752430  Person
objc[23504]: [0x100817130]       0x100752450  Person
objc[23504]: [0x100817138]       0x100752470  Person
objc[23504]: [0x100817140]       0x100752490  Person
objc[23504]: [0x100817148]       0x1007524b0  Person
objc[23504]: [0x100817150]       0x1007524d0  Person
objc[23504]: [0x100817158]       0x1007524f0  Person
objc[23504]: [0x100817160]       0x100752510  Person
objc[23504]: [0x100817168]       0x100752530  Person
objc[23504]: [0x100817170]       0x100752550  Person
objc[23504]: [0x100817178]       0x1007556d0  Person
objc[23504]: [0x100817180]       0x1007556f0  Person
objc[23504]: [0x100817188]       0x100755710  Person
objc[23504]: [0x100817190]       0x100755730  Person
objc[23504]: [0x100817198]       0x100755750  Person
objc[23504]: [0x1008171a0]       0x100755770  Person
objc[23504]: [0x1008171a8]       0x100755790  Person
objc[23504]: [0x1008171b0]       0x1007557b0  Person
objc[23504]: [0x1008171b8]       0x1007557d0  Person
objc[23504]: [0x1008171c0]       0x1007557f0  Person
objc[23504]: [0x1008171c8]       0x100755810  Person
objc[23504]: [0x1008171d0]       0x100755830  Person
objc[23504]: [0x1008171d8]       0x100755850  Person
objc[23504]: [0x1008171e0]       0x100755870  Person
objc[23504]: [0x1008171e8]       0x100755890  Person
objc[23504]: [0x1008171f0]       0x1007558b0  Person
objc[23504]: [0x1008171f8]       0x1007558d0  Person
objc[23504]: [0x100817200]       0x1007558f0  Person
objc[23504]: [0x100817208]       0x100755910  Person
objc[23504]: [0x100817210]       0x100755930  Person
objc[23504]: [0x100817218]       0x100755950  Person
objc[23504]: [0x100817220]       0x100755970  Person
objc[23504]: [0x100817228]       0x100755990  Person
objc[23504]: [0x100817230]       0x1007559b0  Person
objc[23504]: [0x100817238]       0x1007559d0  Person
objc[23504]: [0x100817240]       0x1007559f0  Person
objc[23504]: [0x100817248]       0x100755a10  Person
objc[23504]: [0x100817250]       0x100755a30  Person
objc[23504]: [0x100817258]       0x100755a50  Person
objc[23504]: [0x100817260]       0x100755a70  Person
objc[23504]: [0x100817268]       0x100755a90  Person
objc[23504]: [0x100817270]       0x100755ab0  Person
objc[23504]: [0x100817278]       0x100755ad0  Person
objc[23504]: [0x100817280]       0x100755af0  Person
objc[23504]: [0x100817288]       0x100755b10  Person
objc[23504]: [0x100817290]       0x100755b30  Person
objc[23504]: [0x100817298]       0x100755b50  Person
objc[23504]: [0x1008172a0]       0x100755b70  Person
objc[23504]: [0x1008172a8]       0x100755b90  Person
objc[23504]: [0x1008172b0]       0x100755bb0  Person
objc[23504]: [0x1008172b8]       0x100755bd0  Person
objc[23504]: [0x1008172c0]       0x100755bf0  Person
objc[23504]: [0x1008172c8]       0x100755c10  Person
objc[23504]: [0x1008172d0]       0x100755c30  Person
objc[23504]: [0x1008172d8]       0x100755c50  Person
objc[23504]: [0x1008172e0]       0x100755c70  Person
objc[23504]: [0x1008172e8]       0x100755c90  Person
objc[23504]: [0x1008172f0]       0x100755cb0  Person
objc[23504]: [0x1008172f8]       0x100755cd0  Person
objc[23504]: [0x100817300]       0x100755cf0  Person
objc[23504]: [0x100817308]       0x100755d10  Person
objc[23504]: [0x100817310]       0x100755d30  Person
objc[23504]: [0x100817318]       0x100755d50  Person
objc[23504]: [0x100817320]       0x100755d70  Person
objc[23504]: [0x100817328]       0x100755d90  Person
objc[23504]: [0x100817330]       0x100755db0  Person
objc[23504]: [0x100817338]       0x100755dd0  Person
objc[23504]: [0x100817340]       0x100755df0  Person
objc[23504]: [0x100817348]       0x100755e10  Person
objc[23504]: [0x100817350]  ################  POOL 0x100817350
objc[23504]: [0x100817358]       0x100755e30  Person
objc[23504]: ##############

可以看到當600*8=4800位元組,所以一頁肯定存不下,可以看到

................ PAGE (full) (cold) page 右邊有個 cold、hot。cold 代表不是當前頁,hot 代表當前頁。

繼續看看物件呼叫 autorelease 方法做了什麼事情?

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

inline id objc_object::rootAutorelease() {
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
    return rootAutorelease2();
}
_attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

static inline id autorelease(id obj) {
    assert(obj);
    assert(!obj->isTaggedPointer());
    id *dest __unused = autoreleaseFast(obj);
    assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
    return obj;
}

static inline id *autoreleaseFast(id obj) {
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {
        return page->add(obj);
    } else if (page) {
        return autoreleaseFullPage(obj, page);
    } else {
        return autoreleaseNoPage(obj);
    }
}

檢視 NSObject autorelease 方法呼叫鏈路可以看到最後還是呼叫 AutoreleasePoolPage 的 add 方法(會判斷有沒有頁、有沒有滿)

容器類會自動新增 AutoreleasePool

系統容器類,在使用 block 列舉器的時候,內部會自動建立 AutoreleasePool

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    @autoreleasepool {
        <#statements#>
    }
}];

所以,我們老老實實寫的 for、while 迴圈中需要手加區域性 AutoreleasePool。推薦使用系統提供的容器類的 block 列舉器。

autorelease 物件什麼時候呼叫 release 方法

每當進行一次objc_autoreleasePoolPush呼叫時,runtime向當前的AutoreleasePoolPage中add進一個哨兵物件,值為0(也就是個nil),那麼這一個page就變成了下面的樣子:

objc_autoreleasePoolPush的返回值正是這個哨兵物件的地址,被objc_autoreleasePoolPop(哨兵物件)作為入參,於是:

  1. 根據傳入的哨兵物件地址找到哨兵物件所處的page
  2. 在當前page中,將晚於哨兵物件插入的所有autorelease物件都傳送一次- release訊息,並向回移動next指標到正確位置
  3. 補充2:從最新加入的物件一直向前清理,可以向前跨越若干個page,直到哨兵所在的page

其次,AutoreleasePool 和 RunLoop 的也有關係

iOS 在主執行緒的 Runloop 中註冊了2個 Observer

  • 第1個 Observer 監聽了 kCFRunLoopEntry 事件,會呼叫objc_autoreleasePoolPush()
  • 第2個 Observer 監聽了 kCFRunLoopBeforeWaiting 事件,會呼叫objc_autoreleasePoolPop()objc_autoreleasePoolPush()。還監聽了kCFRunLoopBeforeExit事件,會呼叫 objc_autoreleasePoolPop()

結合 RunLoop 執行圖

  • 01 通知 Observer 進入 Loop 會呼叫 objc_autoreleasePoolPush
  • 做一堆其他事情
  • 07 在將要休眠的時候先呼叫 objc_autoreleasePoolPop,再呼叫 objc_autoreleasePoolPush
  • 等待喚醒做一堆其他事情,回到第二步
  • 07 又開始休眠,先呼叫 objc_autoreleasePoolPop,再呼叫 objc_autoreleasePoolPush
  • 11 沒任務將要休眠,呼叫 objc_autoreleasePoolPop

可以看到 objc_autoreleasePoolPush、objc_autoreleasePoolPop 成對呼叫,貫穿 RunLoop

記憶體問題典型 case

OC 中有沒有不對記憶體進行強持有的集合型別?

NSHashMap、NSMapTable 都可以描述 key、value 的記憶體修飾。

陣列有 NSPointerArray 內部持有的是物件的指標,並非直接儲存物件。不過 oc 轉指標需要加 (__bridge void*) 進行修飾。NSPointerArray 的構造方法中可以透過 NSPointerFunctionsOptions 來宣告記憶體的控制。

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p1 = [[Person alloc] init];
    Person *p2 = [[Person alloc] init];
    Person *p3 = [[Person alloc] init];
    NSPointerArray *arrays = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory];
//    NSMutableArray *array = [NSMutableArray array];
//    [array addObject:p1];
//    [array addObject:p2];
//    [array addObject:p3];
    [arrays addPointer:(__bridge void *)p1];
    [arrays addPointer:(__bridge void *)p2];
    [arrays addPointer:(__bridge void *)p3];
    p1 = nil;
    p2 = nil;
    // 斷點設定到 NSLog,可以看到 Person 馬上釋放了
    NSLog(@"%@", arrays);
}
2022-05-24 21:57:27.071793+0800 TTTTW[63427:2087468] -[Person dealloc]
2022-05-24 21:57:27.071916+0800 TTTTW[63427:2087468] -[Person dealloc]
(lldb) 

NSError 記憶體洩漏的 case

同事問了一個問題,下面的程式碼存在什麼問題?

據說是 Zoom 這個公司的面試題,看了下其實就是考察 NSError 有沒有踩過坑。怎麼理解呢

- (BOOL)isZoomUserWithUserID:(NSInteger)userID error:(NSError **)error
{
    @autoreleasepool {
        NSString *errorMessage = [[NSString alloc] initWithFormat:@"the user is not zoom user"];
        if (userID == 100) {
            *error = [NSError errorWithDomain:@"com.test" code:userID userInfo:@{NSLocalizedDescriptionKey: errorMessage}];
            return NO;
        }
    }
    return YES;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self test];
}

- (void)test {
    for (NSInteger index = 0; index <= 100; index++) {
        NSString *str;
        str = [NSString stringWithFormat:@"welcome to zoom:%ld", index];
        str = [str stringByAppendingString:@" user"];
        NSError *error = NULL;
        if ([self isZoomUserWithUserID:index error:&error]) {
            NSLog(@"%@", str);
        } else {
            NSLog(@"%@", error);
        }
    }
}

這段程式碼執行會 crash,資訊如下

原因是 NSError 構造方法內部會加 autorelease。原始碼如下

#define    AUTORELEASE(object)    [(id)(object) autorelease]
+ (id) errorWithDomain: (NSErrorDomain)aDomain
          code: (NSInteger)aCode
          userInfo: (NSDictionary*)aDictionary
{
  NSError    *e = [self allocWithZone: NSDefaultMallocZone()];

  e = [e initWithDomain: aDomain code: aCode userInfo: aDictionary];
  return AUTORELEASE(e);
}

MRC 下的 [(id)(object) autorelease] 等價於 ARC 下的 id __autoreleasing obj

所以這個問題的本質就是 autoreleasepool__autoreleasing 的問題

__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return.

__autoreleasing 修飾的變數會被新增到當前的 autoreleasepool 中。

方法的 Out Parameters 引數會自動新增 __autoreleasing 屬性。當方法引數裡面有 Out Parameters 引數時,就是有指標的指標型別時,編譯器會自動為引數加上__autoreleasing 屬性。改如下

- (BOOL)isZoomUserWithUserID:(NSInteger)userID error:(NSError **)error
{
    NSError *temp;
    @autoreleasepool {
        NSString *errorMessage = [[NSString alloc] initWithFormat:@"the user is not zoom user"];
        if (userID == 100) {
            temp = [NSError errorWithDomain:@"com.test" code:userID userInfo:@{NSLocalizedDescriptionKey: errorMessage}];
        }
    }
    *error = temp;
    return YES;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self test];
}

- (void)test {
    for (NSInteger index = 0; index <= 100; index++) {
        NSString *str;
        str = [NSString stringWithFormat:@"welcome to zoom:%ld", index];
        str = [str stringByAppendingString:@" user"];
        NSError * __autoreleasing error = NULL;
        if ([self isZoomUserWithUserID:index error:&error]) {
            NSLog(@"%@", str);
        } else {
            NSLog(@"%@", error);
        }
    }
}

我寫了個殭屍物件檢測工具,效果如下

可以定位殭屍物件,並且列印出具體堆疊,並模擬系統行為呼叫 abort 。對監控原理和工具實現感興趣的可以檢視這裡帶你打造一套 APM 監控系統-記憶體監控之野指標/記憶體洩漏監控

Demo ?這裡

記憶體是連續的嗎?

應用啟動後,Mach-O 檔案是分段載入記憶體的。我們使用的記憶體都是虛擬記憶體,透過記憶體對映表來做。

每個程式在建立載入時,會被分配一個大小大概為1~2倍真實地記憶體的連續虛擬地址空間,讓當前軟體認為自己擁有一塊很大記憶體空間。實際上是把磁碟的一小部分作為假想記憶體來使用。

CPU 不直接和實體記憶體打交道,而是透過 MMU(Memory Manage Unit,記憶體管理單元),MMU 是一種硬體電路,速度很快,主要工作是記憶體管理,地址轉換是功能之一。

每個程式都會有自己的頁表 Page Table ,頁表儲存了程式中虛擬地址到實體地址的對映關係,所以就相當於地圖。MMU 收到 CPU 的虛擬地址之後就開始查詢頁表,確定是否存在對映以及讀寫許可權是否正常。

iOS 程式在進行載入時,會根據一 page 大小16kb 將程式分割為多頁,啟動時部分的頁載入進真實記憶體,部分頁還在磁碟中,中間的排程記錄在一張記憶體對映表(Page Table),這個表用來排程磁碟和記憶體兩者之間的資料交換。

如上圖,App 執行時執行某個任務時,會先訪問虛擬頁表,如果頁表的標記為1,則說明該頁面資料已經存在於記憶體中,可以直接訪問。如果頁表為0,則說明資料未在實體記憶體中,這時候系統會阻塞程式,叫做缺頁中斷(page fault),程式會從使用者態切換到核心態,並將缺頁中斷交給核心的 page Fault Handler 處理。等將對應的 page 從磁碟載入到記憶體之後再進行訪問,這個過程叫做 page in。

因為磁碟訪問速度較慢,所以 page in 比較耗時,而且 iOS 不僅僅是將資料載入到記憶體中,還要對這頁做 Code Sign 簽名認證,所以 iOS 耗時更長

Tips:Code Sign 加密雜湊並不少針對於整個檔案,而是針對於每一個 Page 的,保證了在 dyld 進行載入的時候,可以對每一個 page 進行獨立驗證。

等到程式執行時用到了才去記憶體中尋找虛擬地址對應的頁幀,找不到才進行分配,這就是記憶體的惰性(延時)分配機制。

檢測

根據 Instrucments 提供的工具的工作原理,寫一個野指標探針工具去發現並定位問題。具體見野指標監控工具

相關文章