iOS内存管理学习笔记

179 阅读6分钟

一、内存五大区

iOS中,常说的堆区、栈区都是指的虚拟内存,主要分以下五大区

  1. 堆区:由程序员分配和释放,用于存放运行中被动态分配的内存段,大小不定,可以增加或缩减,由低到高,在iOS的ARC程序中,系统自动管理计数器,计数器为0的时候,当次runloop结束后,释放掉内存。堆中的地址通过指针访问,要访问堆中内存时,一般先需要通过栈区的指针地址,然后通过指针地址访问堆区,OC中使用alloc或者new开辟空间创建对象,C语言中使用mallo、calloc、realloc分配空间,free释放,一般0x6开头

  2. 栈区:栈由编译器分配和释放,用于存方程序临时的变量,参数,局部变量等,是一块连续的内存区域,先进后出,由高向低,iOS主线栈大小是1MB,其它线程是512KB,一般0x7开头

  3. 全局区(静态区):全局区分为未初始化区:bss段和初始化全局区:data段,一般0x1开头

  4. 常量区:存放常量,已初始化的全局变量,已初始化的静态变量,空间由系统管理,生命周期为整个程序运行期,一般0x1开头

  5. 代码区:存放函数的二进制代码

22688358-e33e465f0a223d8d.png

二、iOS内存管理的方案

2.1、nonopointerISA

nonopointerISA: 0:纯isa指针,1:不止是类对象地址,isa包含类信息、对象的引用计数等

查看alloc源码调试定位到initIsa

allocWithZone -> _objc_rootAllocWithZone -> _class_createInstanceFromZone -> initIsa

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
  	isa_t newisa(0);
		*****
        newisa.extra_rc = 1;
    }
    isa = newisa;
}

注意到extra_rc,这里extra_rc就是对象的引用计数,说明苹果的优化是把引用计数存储到对象的isa中

护展-isa_t详解

查看ISA结构体位域

#			arm64 //对应iOS移动端
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                     
        uintptr_t nonpointer        : 1;  //表示是否开启指针优化,0:纯isa指针 1:不止是类对象地址,isa包含了类信息、对象的引用计数等                                   
        uintptr_t has_assoc         : 1; // 关联对象标志位,0没有,1存在                                     
        uintptr_t has_cxx_dtor      : 1;  //该对象是否有C++或者Objc的析构器,如果有析构函数,则需有析构逻辑,如果没有,则可以更快的释放对旬
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ //存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
        uintptr_t magic             : 6;  //用于调试器判断当前对象是真的对象还没有初始化空间                                     
        uintptr_t weakly_referenced : 1; //标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。                           
        uintptr_t unused            : 1;//标志对象是否正在释放内存
        uintptr_t has_sidetable_rc  : 1;//当对象引用技术大于 10 时,则需要借用该变量存储进位
        uintptr_t extra_rc          : 19 //当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif
# elif __x86_64__ //对应MacOS端
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ //存储类指针的值。开启指针优化的情况下,在 x86 架构中有 44 位用来存储类指针。
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

2021061023093521.png

2.2、tagged point

5S之后的一种优化,小对象类型 NSNumber NSData NSString,打上tagged标签后,不再是地址,而是真正的值

    NSString *firstString = @"helloworld";
    NSString *secondString = [NSString stringWithFormat:@"helloworld"];
    NSString *thirdString = @"hello";
    NSString *fourthString = [NSString stringWithFormat:@"hello"];
    
    NSLog(@"%p %@",firstString,[firstString class]);
    NSLog(@"%p %@",secondString,[secondString class]);
    NSLog(@"%p %@",thirdString,[thirdString class]);
    NSLog(@"%p %@",fourthString,[fourthString class]);
   
   打印输出
   	2022-07-27 16:12:41.065277+0800 OCTest[53504:3088675] 0x100a7c070 __NSCFConstantString
    2022-07-27 16:12:41.065357+0800 OCTest[53504:3088675] 0x282c556a0 __NSCFString
  	2022-07-27 16:12:41.065386+0800 OCTest[53504:3088675] 0x100a7c090 __NSCFConstantString
  	2022-07-27 16:12:41.065410+0800 OCTest[53504:3088675] 0xb3a7ee3798a9d5ec NSTaggedPointerString

NSCFConstantString:常量区

NSCFString:长字符串通过StringWithFormat创建时会出现,引用计数为1

NSTaggedPointerString:字符串长度小于9时自动生成

tagged point

tagged point:在总是为0的位置打上了标记的指针 -- 真正的值,与随机值混淆

inter最低位为1,arm最高位为1,即表示是tagged point,iOS13以后,最低的三位表示标签

源码分析

源码中搜索_objc_encodeTaggedPointer

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
    if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
        return (void *)ptr;
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag);
    value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
    value |= permutedTag << _OBJC_TAG_INDEX_SHIFT;
#endif
    return (void *)value;
}

可以看到,用objc_debug_taggedpointer_obfuscator和ptr异或运算混淆,搜索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)

其中initializeTaggedPointerObfuscator是在类的加载时调用,初始化一个随机值,用来混淆tagged point指针

那如何查看混淆前的值呢,查看源吗中有_objc_decodeTaggedPointer代码

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;
}

所以我们可以得到编解码的代码

#define lg_OBJC_TAG_INDEX_MASK 0x7UL
#define lg_OBJC_TAG_INDEX_SHIFT 0

extern uintptr_t objc_debug_taggedpointer_obfuscator;
extern uint8_t objc_debug_tag60_permutations[8];

uintptr_t lg_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
lg_objc_decodeTaggedPointer(id ptr)
{
    uintptr_t value = (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
    uintptr_t basicTag = (value >> lg_OBJC_TAG_INDEX_SHIFT) & lg_OBJC_TAG_INDEX_MASK;

    value &= ~(lg_OBJC_TAG_INDEX_MASK << lg_OBJC_TAG_INDEX_SHIFT);
    value |= lg_objc_obfuscatedTagToBasicTag(basicTag) << lg_OBJC_TAG_INDEX_SHIFT;
    return value;
}

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

//混淆编码
void *
lg_objc_encodeTaggedPointer(uintptr_t ptr)
{
    uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);

    uintptr_t basicTag = (value >> lg_OBJC_TAG_INDEX_SHIFT) & lg_OBJC_TAG_INDEX_MASK;
    uintptr_t permutedTag = lg_objc_basicTagToObfuscatedTag(basicTag);
    value &= ~(lg_OBJC_TAG_INDEX_MASK << lg_OBJC_TAG_INDEX_SHIFT);
    value |= permutedTag << lg_OBJC_TAG_INDEX_SHIFT;
    return (void *)value;
}

还原并打印混淆前的地址

NSLog(@"0x%lx",lg_objc_decodeTaggedPointer(fourthString));
//打印输出
0x800037b63632b42a

查看0x800037b63632b42a二进制

(lldb) p/t 0x800037b63632b42a
(unsigned long) $0 = 0b1000000000000000001101111011011000110110001100101011010000101010

其中最高位为1表示是tagged Point,低三位010表示类型

源码搜索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,
		****
};

由此可得010为2,表示是NSString类型

1 - 逐位分析

0000000000000000 - 空白

01101111 - 111-对应ascii码是 o

01101100 - 108-对应ascii码是 l

01101100 - 108-对应ascii码是 l

01100101 - 101-对应ascii码是 e

01101000 - 104-对应ascii码是 h

0101 - 表示长度,hello长度为5

010 - 最低位表示是NSString

由些可见,tagged point是直接存储值

WeChat5cf1d94d92c2d4600d8648afa438dc8a.png

源码查看objc_retain分析

__attribute__((aligned(16), flatten, noinline))
id 
objc_retain(id obj)
{
  //如果是tagedPoint类型直接返回,不走引用计数
    if (_objc_isTaggedPointerOrNil(obj)) return obj;
    return obj->retain();
}

objc_object::retain()
{
    ASSERT(!isTaggedPointer());//断言再次判断是是否是taggedPoint
    return rootRetain(false, RRVariant::FastOrMsgSend);
}

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);
  ......
}
2.3、sidetable

sidetable数据结构

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

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

这个结构中包含一个spinlock_t互斥锁,一个RefcountMap的引用计数表,一个weak_table_t弱引用表

三、源码分析

3.1、objc_retain
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) {
        **********
    }
  
    if (slowpath(!oldisa.nonpointer)) {
        **********
    }
	//存储引用计数由下面的do-while循环实现
    do {
        transcribeToSideTable = false;
        newisa = oldisa;
      //1、不是nonpointerisa -- sidetable
        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;
            }
        }
      //2、是nonopointerias extrc_rc能够存下,extra_rc++
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
			//3、是nonpointerisa extra_rc存不下 -- sidetable
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
          //copy一半的计用计数存到side table
            // 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;
}
3.2、objc_release
objc_release(id obj)
{
    if (_objc_isTaggedPointerOrNil(obj)) return;
    return obj->release();
}

inline void
objc_object::release()
{
    ASSERT(!isTaggedPointer());

    rootRelease(true, RRVariant::FastOrMsgSend);
}

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

   **********
//release的核心方法是下面的do-while循环
retry:
    do {
        newisa = oldisa;
      //1、不是nonpointerisa,直接操作sideTable
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            return sidetable_release(sideTableLocked, performDealloc);
        }
      //2、判断是否在被释放,如果是,直接返回
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            return false;
        }
		//3、extra_rc--,当extra_rc减到0时,跳到underflow
        // 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;
//判断isa中has_sidetable_rc标记位是否为true,如果是,跳转到rootRelease_underflow
    if (slowpath(newisa.has_sidetable_rc)) {
        if (variant != RRVariant::Full) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }
      ****************
    }

deallocate:
    // Really deallocate.

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

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
	//引用计数减到0,发一个dealloc消息
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}
3.3、dealloc
- (void)dealloc {
    _objc_rootDealloc(self);
}

_objc_rootDealloc(id obj)
{
    ASSERT(obj);
    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?,是否是taggedPointer

    if (fastpath(isa.nonpointer                     && //是否是nonpointer
                 !isa.weakly_referenced             && //是否有弱引用
                 !isa.has_assoc                     && 
#if ISA_HAS_CXX_DTOR_BIT
                 !isa.has_cxx_dtor                  && //是否有cxx的析构函数
#else
                 !isa.getClass(false)->hasCxxDtor() && 
#endif
                 !isa.has_sidetable_rc)) //是否has_sidetable_rc为true
    {
        assert(!sidetable_present());
        free(this);//直接free
    } 
    else {
      //有上面的情况执行object_dispose
        object_dispose((id)this);
    }
}

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, /*deallocating*/true);
        obj->clearDeallocating();
    }
    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();
    }
}
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);//清空弱引用表
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);//清空散列表
    }
    table.unlock();
}

四、总结

4.1、 retain总结

1、判断是否是taggedpoint,是就直接返回

2、获取对象的isa

3、判断是否为nonpintISA

4、不是nonpintISA,直接将引用计数存到sidetable

5、是nonpintISA,对象是否正在被释放,直接返回

6、extra_rc能够存下,直接extra_rc++

7、extra_rc存不下,has_sidetable置为yes,将一半的引用计数存入sidetable,extra_rc赋值减半

4.2、 release总结

1、判断是否是taggedpoint,是就直接返回

2、获取对象的isa

3、判断是否为nonpintISA

4、不是nonpintISA,直接操作sidetable,sidetable--,如果为0 dealloc

5、是nonpintISA,对extra_rc--

6、isa extra_rc为0时,判断has_sidetable是否有值

7、has_sidetable有值,获取sidetable引用计数并减1,引用计数赋什给isa,has_sidetable - false

8、如果引用计数为0 -- dealloc

4.3、dealloc总结

1、判断是否是taggedpoint,是就直接返回

2、如果是taggedpoint,而且没有弱引用、没有关联对象、没有c++析构函数,没有散列表引用计数,则直接释放

3、如果是taggedpoint,不是第二步的情况,调用objc_destructInstance

4、删除关联对象,调用c++析构函数,清空弱引用表里面的数据,清空散列表里面的引用计数的信息