iOS-内存管理分析(上)

前言

我们知道内存管理在任何一门编程语言中都有极其重要的地位,即然极其重要,也就意味着有难点,今天我们就来剖析iOS的内存管理相关的知识。

1 内存五大区

内核区用户区。
用户内存五大区:堆、栈、bss(未初始化数据)、data(已初始化数据)、text(代码段)。

  • 栈区:局部变量,方法参数,函数,内存地址一般为:0x7开头
  • 堆区:通过alloc分配的对象,block copy,内存地址一般为:0x6开头
  • BSS段:未初始化的全局变量,静态变量,内存地址一般为:0x1开头
  • 数据段: 初始化的全局变量,静态变量,内存地址一般为:0x1开头
  • text:程序代码,加载到内存中

栈区的内存是通过sp寄存器来定位。
栈区的速度要比堆区速度快

2 内存管理方案

内存管理方案一般有:ARC/MRC
在ARC/MRC比较常见有taggedPointerNONPOINTER_ISA散列表(引用计数表,弱引用表)
我们就来分析下这些内存管理方案。

2.1 taggedPointer底层分析

TaggedPointer:小对象-NSNumber,NSDate
TaggedPointer 是一个指针,会比普通的指针多了Tagged,针对小对象,NSNumber,NSDate,NSString,UIColor,我们存这些对象的时候,其实用不到64位,为了节省内存空间,使用其部分位,小对象=指针 + 存储的内容。

2.1.1 x86-64下的taggedPointer结构

我们看以下代码,来分析taggedPointer在x86上的结构
代码如下

uintptr_t
ro_objc_decodeTaggedPointer(id ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

- (void)indexPathDemo{
   NSString *str = [NSString stringWithFormat:@"ro"];
    NSLog(@"%p-%@-%@",str,str,str.class);
    NSLog(@"%p-%@ -%@- 0x%lx",str,str,str.class,ro_objc_decodeTaggedPointer(str));
}

运行项目,如图


1

我们知道堆区是0x7开头,栈是0x6开头,全局0x1开头,那么这个地址很奇怪,我们来分析下。
我们在objc源码中搜下taggedPointer,如下所示

// payload = (decoded_obj << payload_lshift) >> payload_rshift 

payload就是有效负载,经过位运算,加密和解密的过程。
我们分析一下decoded_obj这个,源码中,搜索decoded,找到如下代码

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;

    value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
    value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
#endif
    return value;
}

_objc_decodeTaggedPointer_noPermute的源码如下

static inline uintptr_t
_objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr)
{
    uintptr_t value = (uintptr_t)ptr;
#if OBJC_SPLIT_TAGGED_POINTERS
    if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
        return value;
#endif
    return value ^ objc_debug_taggedpointer_obfuscator;
}

value ^ objc_debug_taggedpointer_obfuscator;这里进行异或运行,混淆。
我们再找下objc_debug_taggedpointer_obfuscator这个,

/***********************************************************************
* initializeTaggedPointerObfuscator
* Initialize objc_debug_taggedpointer_obfuscator with randomness.
*
* The tagged pointer obfuscator is intended to make it more difficult
* for an attacker to construct a particular object as a tagged pointer,
* in the presence of a buffer overflow or other write control over some
* memory. The obfuscator is XORed with the tagged pointers when setting
* or retrieving payload values. They are filled with randomness on first
* use.
**********************************************************************/
static void
initializeTaggedPointerObfuscator(void)
{
    if (!DisableTaggedPointerObfuscation) {
        // Pull random data into the variable, then shift away all non-payload bits.
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;

#if OBJC_SPLIT_TAGGED_POINTERS
        // The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
        objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);

        // Shuffle the first seven entries of the tag permutator.
        int max = 7;
        for (int i = max - 1; i >= 0; i--) {
            int target = arc4random_uniform(i + 1);
            swap(objc_debug_tag60_permutations[i],
                 objc_debug_tag60_permutations[target]);
        }
#endif
    } else {
        // Set the obfuscator to zero for apps linked against older SDKs,
        // in case they're relying on the tagged pointer representation.
        objc_debug_taggedpointer_obfuscator = 0;
    }
}

initializeTaggedPointerObfuscator这里初始化

arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;

生成一个随机数,在整个内存里是一个encode状态,它的真正地址是需要decode。
通过两个异或(相同为0,不同为1)运算,就可以得到真实的值。

uintptr_t
ro_objc_decodeTaggedPointer(id ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}  

所以这里我们通过这样的异或运算就可以得到真实的值。
运行项目,如图

2

0xa00000000006f722就是真实的指针,我们解析下这个地址。
3

  • 第1位的1代表是taggedPointer类型
    我们再来看下面一张图,如


    4

    114对应o,111对应r,也就是我们的字符串。
    我们再改下代码,如

 NSString *str = [NSString stringWithFormat:@"ro"];
    NSLog(@"%p-%@-%@",str,str,str.class);
    NSLog(@"%p-%@ -%@- 0x%lx",str,str,str.class,ro_objc_decodeTaggedPointer(str));
    
    NSString *str1 = [NSString stringWithFormat:@"r"];

    NSLog(@"%p-%@ -%@- 0x%lx",str1,str1,str1.class,ro_objc_decodeTaggedPointer(str1));

    NSString *str2 = [NSString stringWithFormat:@"robert"];
    NSLog(@"%p-%@ -%@- 0x%lx",str2,str2,str2.class,ro_objc_decodeTaggedPointer(str2));
//
    NSNumber *number = @6;
    NSLog(@"%p-%@ -%@- 0x%lx",number,number,number.class,ro_objc_decodeTaggedPointer(number));

我们看下结果,如图

5

0xa对应的是NSString,0xb对应的是NSNumber,我们来看下objc_tag

{
    // 60-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,

    // 60-bit reserved
    OBJC_TAG_RESERVED_7        = 7, 

    // 52-bit payloads
    OBJC_TAG_Photos_1          = 8,
    OBJC_TAG_Photos_2          = 9,
    OBJC_TAG_Photos_3          = 10,
    OBJC_TAG_Photos_4          = 11,
    OBJC_TAG_XPC_1             = 12,
    OBJC_TAG_XPC_2             = 13,
    OBJC_TAG_XPC_3             = 14,
    OBJC_TAG_XPC_4             = 15,
    OBJC_TAG_NSColor           = 16,
    OBJC_TAG_UIColor           = 17,
    OBJC_TAG_CGColor           = 18,
    OBJC_TAG_NSIndexSet        = 19,
    OBJC_TAG_NSMethodSignature = 20,
    OBJC_TAG_UTTypeRecord      = 21,

    // When using the split tagged pointer representation
    // (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where
    // the tag and payload are unobfuscated. All tags from here to
    // OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache
    // builder is able to construct these as long as the low bit is
    // not set (i.e. even-numbered tags).
    OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set

    OBJC_TAG_Constant_CFString = 136,

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263,

    OBJC_TAG_RESERVED_264      = 264
};

这里就不再一一验证了。

2.1.1 arm64下的taggedPointer

我们再来分析下真机下的效果,相对于模拟器,要复杂一些,代码如下

#define kc_OBJC_TAG_INDEX_MASK 0x7UL
#define kc_OBJC_TAG_INDEX_SHIFT 0

extern uint8_t objc_debug_tag60_permutations[8];

uintptr_t ro_objc_obfuscatedTagToBasicTag(uintptr_t tag) {
    for (unsigned i = 0; i < 7; i++)
        if (objc_debug_tag60_permutations[i] == tag)
            return i;
    return 7;
}

uintptr_t
ro_objc_decodeTaggedPointer(id ptr)
{
    uintptr_t value = (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
    uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK;

    value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT);
    value |= ro_objc_obfuscatedTagToBasicTag(basicTag) << kc_OBJC_TAG_INDEX_SHIFT;

    return value;
}


static inline uintptr_t ro_objc_basicTagToObfuscatedTag(uintptr_t tag) {
    return objc_debug_tag60_permutations[tag];
}

void *
ro_objc_encodeTaggedPointer(uintptr_t ptr)
{
    uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);

    uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK;
    uintptr_t permutedTag = ro_objc_basicTagToObfuscatedTag(basicTag);
    value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT);
    value |= permutedTag << kc_OBJC_TAG_INDEX_SHIFT;
    return (void *)value;
}

真机跟模拟器有不一样的地方。

  • 第1位是标记taggedPointer
  • 类型是存储低三位 char 0,short 1,int 2,3 long,4 float,5 double。
  • 再住走四位,存储的是数据的长度
  • 再往后是数据的内容

由于真机出现问题,暂时无法详细写出验证流程,与模拟器原理相似。

2.1.1 taggedPointer面试题分析

//案例1
- (void)taggedPointerDemo {
  
    self.queue = dispatch_queue_create("com.robert.ccom", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"robert"];
             NSLog(@"%@",self.nameStr);
        });
    }
}

//案例2,连续点击
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    for (int i = 0; i<100000; i++) {
        dispatch_async(self.queue, ^{
         self.nameStr = [NSString stringWithFormat:@"3333333fdsdsfsafsafasfasfasfsafafafafda"];
            NSLog(@"%@",self.nameStr);
        });
    }
}

以上代码的运行会不会产生崩溃?
答案是肯定的。


6
7

分析如下:

  • 案例1 taggedPointer类型的string
  • 案例2 是普通的的string
  • 案例2中会产生多线程的读写操作
  • 案例2中会把setter方法操作,对新值的retain和旧值的release,所以在某一个瞬间会释放多次,产生对野指针的操作
  • 案例2中NSCFString原因是因为数据太长,taggedPointer针对小对象的,根本不受ARC的影响
    部分源码如下
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return (id)this;

if (slowpath(isTaggedPointer())) return (id)this;在这里如果是小针对直接返回,release同样的道理,部分源码如下


ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return false;

  • 小对象的值直接存在指针里面,普通栈结构的回收,根本不需在这里处理
  • 在objc_msgSend中,如果是小对象根本不会进行慢速查找流程

2.2 NONPOINTER_ISA

taggedPointer对类型的处理
NONPOINTER_ISA:非指针型isa
他们都会对位置进行处理,如下

  • nonpointer:表示是否对 isa 指针开启指针优化
    0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等
  • has_assoc:关联对象标志位,0没有,1存在
  • has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
  • shiftcls:存储类指针的值(相当于taggedPointer的payload)。开启指针优化的情况下,在arm64架构中有33位⽤来存储类指针。
  • magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间
  • weakly_referenced:志对象是否被指向或者曾经指向⼀个ARC的弱变量,没有弱引⽤的对象可以更快释放。
  • deallocating:标志对象是否正在释放内存
  • has_sidetable_rc:当对象引⽤技术⼤于10时,则需要借⽤该变量存储进位
  • extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减1,例如,如果对象的引⽤计数为10,那么extra_rc为9。如果引⽤计数⼤于10,则需要使用到has_sidetable_rc。

3 retain及release分析

3.1 MAR&ARC概念

ARC是LLVM和Runtime配合的结果。
ARC中禁⽌⼿动调⽤retain/release/retainCount/dealloc
ARC新加了weak、strong属性关键字

3.2 retain的流程分析

我们在源码中搜下rootRetain,如下源码

ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    oldisa = LoadExclusive(&isa.bits);

    if (variant == RRVariant::FastOrMsgSend) {
        // These checks are only meaningful for objc_retain()
        // They are here so that we avoid a re-load of the isa.
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                return swiftRetain.load(memory_order_relaxed)((id)this);
            }
            return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
        }
    }

    if (slowpath(!oldisa.nonpointer)) {
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) {
            ClearExclusive(&isa.bits);
            return (id)this;
        }
    }

    do {
        transcribeToSideTable = false;
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain(sideTableLocked);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    if (variant == RRVariant::Full) {
        if (slowpath(transcribeToSideTable)) {
            // Copy the other half of the retain counts to the side table.
            sidetable_addExtraRC_nolock(RC_HALF);
        }

        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }

    return (id)this;
}

我们的引用计数存在isa里面,也就是在extra_rc中,在这里引用计数存满时,会放到散列表中。
reatain流程:

  • if (slowpath(isTaggedPointer())) return (id)this;判断是否是taggedPointer,是的话直接返回
  • isa_t oldisa;为了处理extra_rc,下面的do while循环是要着重分析的,
  • if (slowpath(!newisa.nonpointer)) {
    ClearExclusive(&isa.bits);
    if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
    else return sidetable_retain(sideTableLocked);
    }
    这里判断是不是nonpointer,如果是,isa位的操作
  • 如果不是nonpointer,调用sidetable_retain这个函数,
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    if (!locked) table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

获取SideTables的数据,然后执行+ 1<<2操作,也就是在原来的值上+2操作, 为什么是这样的,我们需要分析SideTables的结构。
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1)),WORD_BITS,如果是64位,左移1位,也就是63位,2的二进制是010,其它位置不变,2号位置+1操作,在2号位置存储引用计数的值

  • 如果是nonpointer,执行这段代码
  if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }

*判断是不是在析构,如果在析构就没必要-1操作,在多线程的情况下,已经在释放,还有可能-1。

  • 接着在这里
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

在addc中,拿到bits,执行++操作,# define RC_ONE (1ULL<<45),uintptr_t extra_rc : 19,extra_rc是在最后19位,刚好是64位,左移45位刚好是extra_rc,然后执行++1操作,uintptr_t extra_rc : 8定义,说明extra_rc是8位,2的8次方256位,如果carry加多了,这个时候,执行以下代码

 if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }

在散列表中存储,这里rootRetain_overflow调用这个函数判断所有超载。
接着

 sideTableLocked = true;
 transcribeToSideTable = true;
 newisa.extra_rc = RC_HALF;
 newisa.has_sidetable_rc = true;

执行这段代码,打开锁,newisa.extra_rc = RC_HALF; #define RC_HALF (1ULL<<18),左移18位,也就是移一半,把一半存在存在了extra_rc,另一半标记为has_sidetable_rc,之后调用sidetable_addExtraRC_nolock这个函数,加入到引用计数表中。
所以extra_rc存一半,散列表(引用计数表)存一半。

  • extra_rc存在isa中,直接获取到,散列表需要先查表,size_t& refcntStorage = table.refcnts[this];找到相关对象存储的区域,再移一半。
    通过散列表(引用计数表)增删改查,同时加锁解锁,是比较复杂,效率低。
    那为什么extra_rc还只要存一半?。
  • 如果执行release的--操作时,如果全部存储在散列表中,当满了的情况,去散列表(引用计数表)中去获取,还是有问题,比如300,执行-1操作,299,如果放在extra_rc是放不下的(extra_rc最值256),这样操作比较耗费性能。
  • 所以各存一半,在extra_rc存一半128位,也足够执行--操作了。

3.3 release的流程分析

我们在objc源码搜索,经过搜索找到如下源码

bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return false;

    bool sideTableLocked = false;

    isa_t newisa, oldisa;

    oldisa = LoadExclusive(&isa.bits);

    if (variant == RRVariant::FastOrMsgSend) {
        // These checks are only meaningful for objc_release()
        // They are here so that we avoid a re-load of the isa.
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                swiftRelease.load(memory_order_relaxed)((id)this);
                return true;
            }
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
            return true;
        }
    }

    if (slowpath(!oldisa.nonpointer)) {
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) {
            ClearExclusive(&isa.bits);
            return false;
        }
    }

retry:
    do {
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            return sidetable_release(sideTableLocked, performDealloc);
        }
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            return false;
        }

        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    if (slowpath(newisa.isDeallocating()))
        goto deallocate;

    if (variant == RRVariant::Full) {
        if (slowpath(sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!sideTableLocked);
    }
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;

    if (slowpath(newisa.has_sidetable_rc)) {
        if (variant != RRVariant::Full) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            oldisa = LoadExclusive(&isa.bits);
            goto retry;
        }

        // Try to remove some retain counts from the side table.        
        auto borrow = sidetable_subExtraRC_nolock(RC_HALF);

        bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there

        if (borrow.borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            bool didTransitionToDeallocating = false;
            newisa.extra_rc = borrow.borrowed - 1;  // redo the original decrement too
            newisa.has_sidetable_rc = !emptySideTable;

            bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);

            if (!stored && oldisa.nonpointer) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                uintptr_t overflow;
                newisa.bits =
                    addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
                newisa.has_sidetable_rc = !emptySideTable;
                if (!overflow) {
                    stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
                    if (stored) {
                        didTransitionToDeallocating = newisa.isDeallocating();
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                ClearExclusive(&isa.bits);
                sidetable_addExtraRC_nolock(borrow.borrowed);
                oldisa = LoadExclusive(&isa.bits);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            if (emptySideTable)
                sidetable_clearExtraRC_nolock();

            if (!didTransitionToDeallocating) {
                if (slowpath(sideTableLocked)) sidetable_unlock();
                return false;
            }
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

deallocate:
    // Really deallocate.

    ASSERT(newisa.isDeallocating());
    ASSERT(isa.isDeallocating());

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}
  • if (slowpath(isTaggedPointer())) return false;先判断是不是TaggedPointer的话,直接返回不处理。
  • 在do while中
 if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            return sidetable_release(sideTableLocked, performDealloc);
        }

判断如果不是nonpointer就执行sidetable_release这个函数,对散列表执行release操作,源码如下

uintptr_t
objc_object::sidetable_release(bool locked, bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    if (!locked) table.lock();
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
    auto &refcnt = it.first->second;
    if (it.second) {
        do_dealloc = true;
    } else if (refcnt < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        refcnt |= SIDE_TABLE_DEALLOCATING;
    } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
        refcnt -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return do_dealloc;
}
  • 接着
if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            return false;
        }

判断是不是在析构。

  • 如果是nonpointer,执行以下代码
 uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  //

执行-1操作,如果

 if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }

如果减多了,跳转到这里underflow,如下

if (slowpath(newisa.has_sidetable_rc)) {
        if (variant != RRVariant::Full) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

判断是否有has_sidetable_rc,如果没有跳转到

 if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }

这时给当前对象发送一个dealloc消息。

  • 如果有has_sidetable_rc,执行
 auto borrow = sidetable_subExtraRC_nolock(RC_HALF);

这里代码,取一半,然后

newisa.extra_rc = borrow.borrowed - 1; 

执行--操作。

  • 在sidetable_subExtraRC_nolock函数中
objc_object::SidetableBorrow
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];

    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()  ||  it->second == 0) {
        // Side table retain count is zero. Can't borrow.
        return { 0, 0 };
    }
    size_t oldRefcnt = it->second;

    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
    ASSERT(oldRefcnt > newRefcnt);  // shouldn't underflow
    it->second = newRefcnt;
    return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT };
}

散列中存一半。

3.4 dealloc简单基本流程概述

  • 根据当前对象的状态是否直接调用free()释放
  • 是否在瞎了眼在C++的析构函数,移除这个对象的关联属性
  • 将指向该对象的弱引用指针置为nil
  • 从弱引用表中移除该对象的引用计数

总结

这次我们介绍了内存的五大区,taggedPointer,retain,release的底层分析,希望些文章可以让大家对iOS的内存管理有一个新的认识,该文章略有粗糙,还望谅解。

你可能感兴趣的:(iOS-内存管理分析(上))