美文网首页
iOS内存管理(一)、内存分区和引用计数

iOS内存管理(一)、内存分区和引用计数

作者: 默默_David | 来源:发表于2020-06-02 21:53 被阅读0次

    iOS内存管理(一)、内存分区和引用计数
    iOS内存管理(二)alloc、retain、release、dealloc

    一、内存分区

    内存布局

    0xc0000000转化出来,正好为3GB,所以我们的运行内存最多为3GB
    在动态分配内存的时候,栈区的栈帧不断往下走,而堆区随着内存开辟越多会不断往上走,当它们重合的时候,就形成了堆栈溢出。

    在dyld加载可执行文件到内存的时候,它会将加载的数据给分别存放到.bss、.data、.text段。

    iOS的内存分区指RAM中的内存分区,它主要分为五大区:

    内存分区

    二、内存管理方案 isa_t

    TaggedPointer:小对象-NSNumber,NSDate以及长度短的NSSTring
    NONPOINTER_ISA:非指针型isa
    散列表:引用计数表、弱引用表

    在这里我们先介绍一下isa,它在源码中的结构为(只看arm64的)

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
        struct {
          //0表示普通的isa指针,1表示使用优化,存储引用计数
          uintptr_t nonpointer        : 1;                                       \
          //表示该对象是否包含关联对象,如果没有,则析构时会更快
          uintptr_t has_assoc         : 1;                                       \
          //表示该对象是否有C++或ARC的析构函数,如果没有,则析构时更快
          uintptr_t has_cxx_dtor      : 1;                                       \
          //类的指针
          uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
          //固定值为0xd2,用于在调试时分辨对象是否未完成初始化
          uintptr_t magic             : 6;                                       \
          //表示该对象是否有过weak对象,如果没有,则析构时更快
          uintptr_t weakly_referenced : 1;                                       \
          //表示该对象是否正在析构
          uintptr_t deallocating      : 1;                                       \
          //表示该对象的引用计数值是否过大需要额外存储到sidetable中
          uintptr_t has_sidetable_rc  : 1;                                       \
          //存储最多2^8-1的引用计数值,存储sidetable以外的引用计数值减1的结果
          uintptr_t extra_rc          : 19
        };
    };
    

    三、TaggedPointer

    TaggedPointer常用来存储小对象如NSNumber,NSDate以及长度短的NSSTring,TaggedPointer指针的值不再是地址了,而是真正的值,所以实际上它不再是一个对象了,它只是披着对象皮的普通变量而已!所以,它的内存并不存储在堆中,也不需要malloc和free
    TaggedPointer内存读取是读取对象的3倍,创建对象过程是创建对象的106倍
    一般TaggedPointer的打印的结构为tag+值+值类型

    源码中,如果为taggedPointer,直接返回非类类型

    inline bool objc_object::isClass()
    {
        //如果是TaggedPointer,返回false
        if (isTaggedPointer()) return false;
        return ISA()->isMetaClass();
    }
    
    inline bool objc_object::isTaggedPointer() 
    {
        return _objc_isTaggedPointer(this);
    }
    
    static inline bool _objc_isTaggedPointer(const void * _Nullable ptr)
    {
        return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
    }
    define _OBJC_TAG_MASK (1UL<<63)
    
    

    _objc_isTaggedPointer函数中,将自身指针值与1...0进行按位与操作,如果还是1...0,则是使用了taggedPointer机制,也就是说,在iOS中,判断是否taggedPoint,就是看其最高位是否为1
    拓展:在MACOS中,由于define _OBJC_TAG_MASK 1UL,所以,判断在MACOS中是否为taggedPoint,就是看其最低位是否为1

    我们再看这个函数

    static inline void * _Nonnull
    _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
    {
        if (tag <= OBJC_TAG_Last60BitPayload) {
            uintptr_t result =
                (_OBJC_TAG_MASK | 
                 ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
                 ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
            return _objc_encodeTaggedPointer(result);
        } else {
            uintptr_t result =
                (_OBJC_TAG_EXT_MASK |
                 ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
                 ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
            return _objc_encodeTaggedPointer(result);
        }
    }
    

    我们不看其它部分,最终不论是哪一种形式,最后函数都会调用_objc_encodeTaggedPointer(result),我们再来看这个函数

    uintptr_t objc_debug_taggedpointer_obfuscator = 0;
    //编码方法
    static inline void * _Nonnull_objc_encodeTaggedPointer(uintptr_t ptr)
    {
        return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
    }
    //解码方法
    static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr)
    {
        return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
    }
    

    编码方法中,与0进行按位异或进行编码,解码方法中,同样与0进行按位异或进行解码

    在最新版的objc4-781源码中,关于objc_debug_taggedpointer_obfuscator进行更多一步的操作,在_read_images方法中,调用initializeTaggedPointerObfuscator()objc_debug_taggedpointer_obfuscator进行了初始化(做了代码混淆),我们看看这个函数的源码:

    static void
    initializeTaggedPointerObfuscator(void)
    {
        if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || DisableTaggedPointerObfuscation) {
            //老版本直接为0
            objc_debug_taggedpointer_obfuscator = 0;
        } else {
            //新版本给一个随机数,和_OBJC_TAG_MASK按位取反后的值进行与操作
            arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                           sizeof(objc_debug_taggedpointer_obfuscator));
            objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
        }
    }
    define _OBJC_TAG_MASK (1UL<<63)
    

    所以按照现在的源码结构,我们如果想要看到原来的tag+值+值类型结构,需要我们将_objc_decodeTaggedPointer方法拷贝出来,自己对它进行解码操作,

    
    
    - (void)taggedPointerTest{
        int num1 = 22;
        long num2 = 55;
        float num3 = 2.0f;
        double num4 = 4.0;
        double num5 = 22.33;
        NSNumber *number1 = @(num1);
        NSNumber *number2 = @(num2);
        NSNumber *number3 = @(num3);
        NSNumber *number4 = @(num4);
        NSNumber *number5 = @(num5);
        
        NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer(number1));
        NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number2),number2,number2,_objc_decodeTaggedPointer(number2));
        NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number3),number3,number3,_objc_decodeTaggedPointer(number3));
        NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number4),number4,number4,_objc_decodeTaggedPointer(number4));
        NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number5),number5,number5,_objc_decodeTaggedPointer(number5));
    
    }
    extern uintptr_t objc_debug_taggedpointer_obfuscator;
    uintptr_t
    _objc_decodeTaggedPointer(id ptr)
    {
        return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
    }
    
    //打印结果
    // 所属类:__NSCFNumber---指针:0xfd8919a30310b479---值:22---0xb000000000000162
    // 所属类:__NSCFNumber---指针:0xfd8919a30310b668---值:55---0xb000000000000373
    // 所属类:__NSCFNumber---指针:0xfd8919a30310b53f---值:2---0xb000000000000024
    // 所属类:__NSCFNumber---指针:0xfd8919a30310b55e---值:4---0xb000000000000045
    // 所属类:__NSCFNumber---指针:0x6000013d0d80---值:22.33---0x4d8979a3022db89b
    
    

    我们可以看到指针打印出来的值已经没有原来的那种含义,因为现在源码中已做了代码混淆。而在我们自己解码出来的值中,可以看到最后一位代表它的类型,2表示int类型,3代表long类型,4代表float类型,5代表double类型,而对于复杂的浮点数,如num5,最后一位并不能确定其类型。除此以外,在类型前面则显示的是它的值的16进制值。

    NONPOINTER_ISA

    若isa_t中位域中的nonpointer为1,表示优化过的isa,用于存储引用计数

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
        struct {
          //0表示普通的isa指针,1表示使用优化,存储引用计数
          uintptr_t nonpointer        : 1;                                       \
          //表示该对象是否包含关联对象,如果没有,则析构时会更快
          uintptr_t has_assoc         : 1;                                       \
          //表示该对象是否有C++或ARC的析构函数,如果没有,则析构时更快
          uintptr_t has_cxx_dtor      : 1;                                       \
          //类的指针
          uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
          //固定值为0xd2,用于在调试时分辨对象是否未完成初始化
          uintptr_t magic             : 6;                                       \
          //表示该对象是否有过weak对象,如果没有,则析构时更快
          uintptr_t weakly_referenced : 1;                                       \
          //表示该对象是否正在析构
          uintptr_t deallocating      : 1;                                       \
          //表示该对象的引用计数值是否过大需要额外存储到sidetable中
          uintptr_t has_sidetable_rc  : 1;                                       \
          //存储最多2^8-1的引用计数值,存储sidetable以外的引用计数值减1的结果
          uintptr_t extra_rc          : 19
        };
    };
    

    现在我们来研究retainCount在源码中的实现

    - (NSUInteger)retainCount {
        return _objc_rootRetainCount(self);
    }
    
    uintptr_t _objc_rootRetainCount(id obj)
    {
        //判断对象是否为空,为空断在这里
        ASSERT(obj);
        return obj->rootRetainCount();
    }
    inline uintptr_t objc_object::rootRetainCount()
    {
        //如果是taggedPointer 直接返回指针值
        if (isTaggedPointer()) return (uintptr_t)this;
    
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);
        ClearExclusive(&isa.bits);
        //如果是优化后的指针(存储引用计数)
        if (bits.nonpointer) {
            //将extra_rc值加1,rc是引用计数
            uintptr_t rc = 1 + bits.extra_rc;
            //如果有sidetable
            if (bits.has_sidetable_rc) {
                //获取sidetable中存储的引用计数值
                rc += sidetable_getExtraRC_nolock();
            }
            sidetable_unlock();
            return rc;
        }
    
        sidetable_unlock();
        //如果不是优化后的指针,那么引用计数从sidetable中返回
        return sidetable_retainCount();
    }
    
    //从sideTable中取出引用计数值,不上锁,而且不加1,因为在前面的函数中已经+1
    size_t objc_object::sidetable_getExtraRC_nolock()
    {
        ASSERT(isa.nonpointer);
        SideTable& table = SideTables()[this];
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it == table.refcnts.end()) return 0;
        //从refcnts中取出值,然后右移两位获取到引用计数
        //这里为什么要右移两位,因为最低位存储了是否有弱引用,低二位存储了SideTable是否在析构,所以需要右移两位
        else return it->second >> SIDE_TABLE_RC_SHIFT;
    }
    
    //从sideTable中取出引用计数值,上锁,而且要加1
    uintptr_t objc_object::sidetable_retainCount()
    {
        SideTable& table = SideTables()[this];
    
        size_t refcnt_result = 1;
        
        table.lock();
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it != table.refcnts.end()) {
            //从refcnts中取出值,然后右移两位获取到引用计数
            //这里为什么要右移两位,因为最低位存储了是否有弱引用,低二位存储了SideTable是否在析构,所以需要右移两位
            refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
        }
        table.unlock();
        return refcnt_result;
    }
    #define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
    #define SIDE_TABLE_DEALLOCATING      (1UL<<1) 
    #define SIDE_TABLE_RC_ONE            (1UL<<2) 
    #define SIDE_TABLE_RC_SHIFT 2
    
    //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(); }
        template<HaveOld, HaveNew>
        static void lockTwo(SideTable *lock1, SideTable *lock2);
        template<HaveOld, HaveNew>
        static void unlockTwo(SideTable *lock1, SideTable *lock2);
    };
    
    //SideTable中RefcountMap的定义,它是一张哈希表
    typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;
    
    

    RefcountMap refcnts:它是一张哈希表,查询的时候使用对象的地址作为key,经过哈希算法,得到一个值,这个值的最低位存储了是否有弱引用,低二位存储了是否正在析构,将这个值右移两位,如果nonpointer为0,右移两位后加1,则是引用计数值;如果nonpointer为1,我们从isa_t的extra_rc中取出值,加1,再加上右移两位的值,则得到了引用计数值

    从上面的源码中,对于非TaggedPointer对象,我们可以得出以下结论:

    • 如果nonpointer为0,表示isa未优化过,不作为存储引用计数,那么引用计数值都存放在SideTable中成员refcnts中,从refcnts中取出值加1,则得到了我们的引用计数值
    • 如果nonpointer为1,表示isa优化过,存储了部分引用计数值,我们从isa_t的extra_rc中取出值,加1,再从SideTable中成员refcnts中取出值右移两位,加上前面的值,则得到了引用计数值

    相关文章

      网友评论

          本文标题:iOS内存管理(一)、内存分区和引用计数

          本文链接:https://www.haomeiwen.com/subject/zeluzhtx.html