iOS Block引用計數及內存管理

Block是在iOS4引入的新特性,是一種特殊的數據類型,今天我們就從源碼層面探索一下Block具體是一種什麼類型,並探尋下Block的內存管理方式。

一、Block類型

對於Block是什麼類型,其實網上已經給出了答案,那就是Block實例也是一種對象。這個觀點是完全正確的,我們可以從以下兩個方面進行驗證:

1. 源碼

目前關於Block的源碼是公開的,具體下載位置爲地址

對於Block,本質上是一個結構體,其內容如下:

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

Block_layoutisa指針可以得出以上結論。

2. clang轉換後的代碼

我們可以簡單寫一個Block:

void(^block1)(void) = ^(void) {
    NSLog(@"block1");
};

通過clang -rewrite-objc main.m得到轉換後的代碼,其中與該Block有關的內容如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_hb_tnc4751s73b8_zwpzmxttpvm0000gn_T_main_f377bb_mi_0);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

void(*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

可以看到該Block本身就是一個__main_block_impl_0的結構體,而該結構體中第一個成員便是struct __block_impl

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

可以看到,雖然轉換出來的代碼最終是__block_impl結構體,但他與源碼的結構體內容完全一致,且第一個成員爲isa指針,同樣可以證明Block變量就是一個對象。

二、Block引用計數

既然Block也是一種對象,那它是否也遵循對象的引用計數方式呢?

我們從runtime的SideTable源碼並沒有找到任何關於Block引用計數的相關代碼,但這並不表示Block和引用計數沒有關聯,Block與引用計數的關聯其實很簡單,就是保持在struct Block_layout結構體本身中:

volatile int32_t flags; // contains ref count

flags成員的註釋中表明瞭它保持了引用計數的內容,那麼他是如何保存的呢?

struct Block_layout聲明的代碼上部就有一些關於flags標誌的定義:

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

結合這些標誌,我們可以得出flags成員的含義:

flags二進制下標
31 30 29 28 27 26 25 24 23 ... 1 0
代表含義
是否有擴展布局 是否有簽名 返回值是否在棧上 是否是全局Block 是否採用垃圾回收機制 helper是否有C++代碼 是否有copy/dispose方法 是否需要釋放,即存在與堆上 引用計數 是否正在釋放

由以上源碼可知,Block的引用計數保存在32位flags的第1-23位上,同時flags也指定了Block是否需要釋放等信息。

既然有是否需要釋放這個信息,那麼肯定就存在Block需要釋放與不需要釋放兩種情況,那麼這兩種情況是如何出現的,我們需要從Block的分類開始說起。

三、Block分類

Block既然有isa指針,那麼Block屬於哪種對象呢?

從源碼中我們可以得到以下分類:

void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };

Block共有以上5中類型,其中可以分爲以下幾種情況:

_NSConcreteGlobalBlock & _NSConcreteStackBlock

在官方文檔中,有以下一段描述:

void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock

文檔表明Block在創建時,只能創建出_NSConcreteStackBlock與_NSConcreteGlobalBlock兩種類型的Block,那麼如何創建這兩種類型的Block,網上已經有不少文章指出了,關鍵點就在Block是否需要引用外部變量,若未引用,則爲_NSConcreteGlobalBlock,若引用,則爲_NSConcreteStackBlock。

_NSConcreteMallocBlock

在源碼中,搜索_NSConcreteMallocBlock,發現只有一處代碼有isa = _NSConcreteMallocBlock,且代碼位於_Block_copy中:

//libclosure-74
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

由源碼可知,當_NSConcreteStackBlock調用copy方法的時候,纔會變爲_NSConcreteMallocBlock。且這是創建_NSConcreteMallocBlock的唯一途徑。

同時可以看出,在創建_NSConcreteMallocBlock類型的Block時,是通過malloc方法申請的堆內存,說明該類型的Block位於堆上。

_NSConcreteAutoBlock & _NSConcreteFinalizingBlock

這兩種類型的Block創建方式在源碼中同樣只有一處,在_Block_copy_internal中:

//libclosure-63
static void *_Block_copy_internal(const void *arg, const bool wantsOne) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GC) {
        // GC refcounting is expensive so do most refcounting here.
        if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 2)) {
            // Tell collector to hang on this - it will bump the GC refcount version
            _Block_setHasRefcount(aBlock, true);
        }
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    // Its a stack block.  Make a copy.
    if (!isGC) {
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        result->isa = _NSConcreteMallocBlock;
        _Block_call_copy_helper(result, aBlock);
        return result;
    }
    else {
        // Under GC want allocation with refcount 1 so we ask for "true" if wantsOne
        // This allows the copy helper routines to make non-refcounted block copies under GC
        int32_t flags = aBlock->flags;
        bool hasCTOR = (flags & BLOCK_HAS_CTOR) != 0;
        struct Block_layout *result = _Block_allocator(aBlock->descriptor->size, wantsOne, hasCTOR || _Block_has_layout(aBlock));
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        // if we copy a malloc block to a GC block then we need to clear NEEDS_FREE.
        flags &= ~(BLOCK_NEEDS_FREE|BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);   // XXX not needed
        if (wantsOne)
            flags |= BLOCK_IS_GC | 2;
        else
            flags |= BLOCK_IS_GC;
        result->flags = flags;
        _Block_call_copy_helper(result, aBlock);
        if (hasCTOR) {
            result->isa = _NSConcreteFinalizingBlock;
        }
        else {
            result->isa = _NSConcreteAutoBlock;
        }
        return result;
    }
}

由源碼可知,在使用垃圾回收機制時纔會創建這兩種類型的Block,且當涉及C++代碼的helper會生成_NSConcreteFinalizingBlock類型的Block,其他情況下生成_NSConcreteAutoBlock類型的Block。

這兩種類型的Block我們可以不用關注了,在iOS上並沒有垃圾回收機制,同時在最新的libclosure代碼中,這兩種類型的Block已經沒有創建路徑了,即不可能創建出這兩種類型的Block了。

四、內存管理

爲了能夠直觀的觀察Block引用計數的變化,我們模擬構造與源碼佈局一致的結構體,這樣便於直接觀察flags的變化:

enum {
    MBlock_REFCOUNT_MASK        = (0xfffe),
    MBlock_HAS_COPY_DISPOSE     = (1 << 25),
    MBlock_HAS_CTOR             = (1 << 26),
    MBlock_IS_GLOBAL            = (1 << 28),
    MBlock_HAS_STRET            = (1 << 29),
    MBlock_HAS_SIGNATURE        = (1 << 30),
};

struct MBlockLiteral {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct MBlockDecriptor1 *descriptor;
};

struct MBlockDescriptor1 {
    uintptr_t reserved;
    uintptr_t size;
};

同時我們知道Block本質上是一個對象,那麼涉及到內存管理的方法就只有retainrelease以及copy三個方法了,我們接下來就從這三個方法進行研究。

從源碼中,我們可以找尋到這三個方法對應的原始方法,分別爲:bool _Block_tryRetain(const void *arg)void _Block_release(const void *arg)以及void *_Block_copy(const void *arg)

爲了方便觀察三個方法的調用情況,我們使用fishhook對這三個方法進行hook:

static bool hook_Block_tryRetain(const void *arg);
bool (*origin_Block_tryRetain)(const void *arg);

static void * hook_Block_copy(const void *arg);
void *(*origin_Block_copy)(const void *arg);

static void hook_Block_release(const void *arg);
void (*origin_Block_release)(const void *arg);


static bool hook_Block_tryRetain(const void *arg) {
    NSLog(@"Block_tryRetain %@", (__bridge id)arg);
    return origin_Block_tryRetain(arg);
}

static void * hook_Block_copy(const void *arg) {
    NSLog(@"Block_copy %@", (__bridge id)arg);
    return origin_Block_copy(arg);
}

static void hook_Block_release(const void *arg) {
    NSLog(@"Block_release %@", (__bridge id)arg);
    origin_Block_release(arg);
}

struct rebinding rebindBlock_tryRetain;
rebindBlock_tryRetain.name = "_Block_tryRetain";
rebindBlock_tryRetain.replacement = hook_Block_tryRetain;
rebindBlock_tryRetain.replaced = (void *)&origin_Block_tryRetain;
struct rebinding rebindBlock_copy;
rebindBlock_copy.name = "_Block_copy";
rebindBlock_copy.replacement = hook_Block_copy;
rebindBlock_copy.replaced = (void *)&origin_Block_copy;
struct rebinding rebindBlock_release;
rebindBlock_release.name = "_Block_release";
rebindBlock_release.replacement = hook_Block_release;
rebindBlock_release.replaced = (void *)&origin_Block_release;
struct rebinding rebs[3] = {rebindBlock_tryRetain, rebindBlock_copy, rebindBlock_release};
rebind_symbols(rebs, 3);

_NSConcreteGlobalBlock

我們創建一個全局Block,分別調用三個內存管理方法,然後觀察輸入日誌:

void(^block1)(void) = ^(void) {
    NSLog(@"block1");
};
struct MBlockLiteral *block1Ref = (__bridge struct MBlockLiteral *)block1;
NSLog(@"block1---%@ %lu", block1, (unsigned long)block1Ref->flags & MBlock_REFCOUNT_MASK);
[block1 retain];
NSLog(@"block1---%@ %lu", block1, (unsigned long)block1Ref->flags & MBlock_REFCOUNT_MASK);
[block1 copy];
NSLog(@"block1---%@ %lu", block1, (unsigned long)block1Ref->flags & MBlock_REFCOUNT_MASK);
[block1 release];
NSLog(@"block1---%@ %lu", block1, (unsigned long)block1Ref->flags & MBlock_REFCOUNT_MASK);

輸出爲:

block1---<__NSGlobalBlock__: 0x106132050> 0
block1---<__NSGlobalBlock__: 0x106132050> 0
block1---<__NSGlobalBlock__: 0x106132050> 0
block1---<__NSGlobalBlock__: 0x106132050> 0

可以看出,對於全局Block來說,在調用三個內存管理方法時,並未觸發相關方法,即對於全局Block來說,調用retainreleasecopy來說,是無效的。

同時可以看出,全局Block的引用計數一直保持在0。

_NSConcreteStackBlock

我們創建一個棧Block,分別調用retainrelease(由於copy方法會產生堆Block,我們將copy放在堆Block中討論)。

int a = 0;
void(^block2)(void) = ^(void) {
    NSLog(@"block2 %d", a);
};
struct MBlockLiteral *block2Ref = (__bridge struct MBlockLiteral *)block2;
NSLog(@"block2---%@ %lu", block2, (unsigned long)block2Ref->flags & MBlock_REFCOUNT_MASK);
[block2 retain];
NSLog(@"block2---%@ %lu", block2, (unsigned long)block2Ref->flags & MBlock_REFCOUNT_MASK);
[block2 release];
NSLog(@"block2---%@ %lu", block2, (unsigned long)block2Ref->flags & MBlock_REFCOUNT_MASK);

輸出爲:

block2---<__NSStackBlock__: 0x7ffeef0de0a0> 0
block2---<__NSStackBlock__: 0x7ffeef0de0a0> 0
block2---<__NSStackBlock__: 0x7ffeef0de0a0> 0

可以看出,對於棧Block來說,調用retainrelease是,也未觸發相關方法,即對於棧Block來說,調用retainrelease來說,是無效的。

同時可以看出,此Block的地址較高,位於棧區,引用指數也一直保持在0,釋放時機由系統控制。

_NSConcreteMallocBlock

我們由棧Block通過copy方法創建一個堆Block,並分別調用retainreleasecopy

void(^block3)(void) = ^(void) {
    NSLog(@"block3 %d", a);
};
block3 = [block3 copy];
struct MBlockLiteral *block3Ref = (__bridge struct MBlockLiteral *)block3;
NSLog(@"block3---%@ %lu", block3, (unsigned long)block3Ref->flags & MBlock_REFCOUNT_MASK);
[block3 retain];
NSLog(@"block3---%@ %lu", block3, (unsigned long)block3Ref->flags & MBlock_REFCOUNT_MASK);
[block3 copy];
NSLog(@"block3---%@ %lu", block3, (unsigned long)block3Ref->flags & MBlock_REFCOUNT_MASK);
[block3 release];
NSLog(@"block3---%@ %lu", block3, (unsigned long)block3Ref->flags & MBlock_REFCOUNT_MASK);
[block3 release];
NSLog(@"block3---%@ %lu", block3, (unsigned long)block3Ref->flags & MBlock_REFCOUNT_MASK);

輸出爲:

Block_copy <__NSStackBlock__: 0x7ffee4514068>
2020-06-17 16:20:28.750074+0800 TheTestTest[26212:315120] block3---<__NSStackBlock__: 0x7ffee4514068> 0
2020-06-17 16:20:28.750142+0800 TheTestTest[26212:315120] block4---<__NSMallocBlock__: 0x600001122340> 2
2020-06-17 16:20:28.750201+0800 TheTestTest[26212:315120] Block_copy <__NSMallocBlock__: 0x600001122340>
2020-06-17 16:20:28.750453+0800 TheTestTest[26212:315120] block4---<__NSMallocBlock__: 0x600001122340> 4
2020-06-17 16:20:28.750522+0800 TheTestTest[26212:315120] Block_copy <__NSMallocBlock__: 0x600001122340>
2020-06-17 16:20:28.750585+0800 TheTestTest[26212:315120] block4---<__NSMallocBlock__: 0x600001122340> 6
2020-06-17 16:20:28.750648+0800 TheTestTest[26212:315120] Block_release <__NSMallocBlock__: 0x600001122340>
2020-06-17 16:20:28.750713+0800 TheTestTest[26212:315120] block4---<__NSMallocBlock__: 0x600001122340> 4
2020-06-17 16:20:28.750778+0800 TheTestTest[26212:315120] Block_release <__NSMallocBlock__: 0x600001122340>
2020-06-17 16:20:28.750838+0800 TheTestTest[26212:315120] block4---<__NSMallocBlock__: 0x600001122340> 2

由以上輸出可知:

  1. 將棧Block通過copy方法複製到堆上時,調用的是_Block_copy方法,在copy之後,棧Block的引用計數還是0,堆Block的引用計數變爲2;
  2. 爲堆Block調用retain和copy方法,調用的均是_Block_copy方法,調用後,堆Block的引用計數增加2;
  3. 爲堆Block調用release方法,調用的是_Block_release方法,調用後,堆Block的引用計數減少2;

通過以上結論,我們可知,堆Block的內存管理集中在_Block_copy_Block_release兩個方法中,我們來看一下兩個方法的實現,其中的邏輯我已做出註釋:

//libclosure-74
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    // 若傳入空,直接返回
    if (!arg) return NULL;
    
    aBlock = (struct Block_layout *)arg;
    // 若Block的BLOCK_NEEDS_FREE標誌爲1,表示Block爲堆Block,則調用latching_incr_int方法
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 若Block是全局Block,直接返回
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // 若Block是棧Block,則拷貝至堆區
        
        // 在堆區申請空間
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        
        // 拷貝內容
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // 設置回調指針
        result->invoke = aBlock->invoke;
#endif
        // 將flag的1-23位引用計數標誌位以及第0位是否正在釋放位重置
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
        // 設置flag的BLOCK_NEEDS_FREE位置爲1,表示此時處於堆區;同時與2,將第1位置爲1,表示引用計數此時爲1
        result->flags |= BLOCK_NEEDS_FREE | 2;
        // 調用copy helper
        _Block_call_copy_helper(result, aBlock);
        // 設置Block的isa指針爲堆Block
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        // 若flags的第1-23位全部爲1(即引用計數到達最大值)時,不做操作,直接返回
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        // 將flags的值增加2,即從第1位開始增加1,表示引用計數加1
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}
//libclosure-74
void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    // 如果傳入Block爲空,直接返回
    if (!aBlock) return;
    
    // 如果傳入的Block爲全局Block,直接返回
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    
    // 如果傳入的Block不是堆Block,即是棧Block,直接返回
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
    
    // 調用latching_decr_int_should_deallocate減少引用計數
    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        // 調用dispose helper
        _Block_call_dispose_helper(aBlock);
        // 釋放堆區內存
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}

static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        // 若flags的第1-23位全部爲1(即引用計數到達最大值)時,不做操作,直接返回
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return false;
        }
        // 若flags的第1-23位全部爲0(即引用計數爲0)時,不做操作,直接返回
        if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
            return false;
        }
        // 將flags的值減少2,即從第1位開始減少1,表示引用計數減1
        int32_t new_value = old_value - 2;
        bool result = false;
        
        // 若此時引用計數剛好是2,即計數爲1,將flags減爲1,此時第1位爲0,第0位置爲1,表示引用計數爲0並正在釋放
        if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
            new_value = old_value - 1;
            result = true;
        }
        if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
            return result;
        }
    }
}

源碼邏輯並不複雜,在_Block_copy中,有對全局Block的空處理,在_Block_release中,有對全局Block和棧Block的空處理,從之前的分析我們可知,這些處理其實永遠不用執行,但源碼同樣做了異常保護。

在每次增加/減少引用計數時,是將flags增加/減少2,這是由於flags的第1-23位保存的是引用計數,需要對第0位進行偏移。

以上就是Block引用計數機制及各個分類Block內存管理的實現原理,底部實現其實非常有設計性,使用一個32位的flags可以記錄Block的各種信息,進而減少內存的使用。

如有興趣,可以去研究一下Object的引用計數機制,它同樣也是採用一個flag記錄多個信息,其中引用計數是從第2位開始的,所以每次都是以4爲單位進行增減的,但不一樣的是,它的引用計數並沒有保存在自身結構體中,而是保存在SideTable中的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章