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

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
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

//SideTable中RefcountMap的定义,它是一张哈希表
typedef objc::DenseMap,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内存管理(一)、内存分区和引用计数)