iOS底层原理探索 — 内存管理(二)

探索底层原理,积累从点滴做起。大家好,我是Mars。

往期回顾

iOS底层原理探索 — OC对象的本质
iOS底层原理探索 — class的本质
iOS底层原理探索 — KVO的本质
iOS底层原理探索 — KVC的本质
iOS底层原理探索 — Category的本质(一)
iOS底层原理探索 — Category的本质(二)
iOS底层原理探索 — 关联对象的本质
iOS底层原理探索 — block的本质(一)
iOS底层原理探索 — block的本质(二)
iOS底层原理探索 — Runtime之isa的本质
iOS底层原理探索 — Runtime之class的本质
iOS底层原理探索 — Runtime之消息机制
iOS底层原理探索 — RunLoop的本质
iOS底层原理探索 — RunLoop的应用
iOS底层原理探索 — 多线程的本质
iOS底层原理探索 — 多线程的经典面试题
iOS底层原理探索 — 多线程的“锁”
iOS底层原理探索 — 多线程的读写安全
iOS底层原理探索 — 内存管理(一)

前言

内存管理在APP开发过程中占据着一个很重要的地位,在iOS中,系统为我们提供了ARC的开发环境,帮助我们做了很多内存管理的内容,其实在MRC时代,内存管理对于开发者是个很头疼的问题。我们会通过几篇文章的分析,来帮助我们了解iOS中内存管理的原理,以及在ARC的开发环境下系统帮助我们做了哪些内存管理的操作。

在iOS中,系统为我们提供了一下三种内存管理方法:

  • Tagged Pointer技术
  • NONPOINTER_ISA
  • 散列表

其中,Tagged Pointer技术和NONPOINTER_ISA我们在iOS底层原理探索 — 内存管理(一)一文中有简单介绍,这里不在赘述。本文主要针对散列表展开分析。

散列表

我们在iOS底层原理探索 — 内存管理(一)一文中介绍引用计数时讲到,在NONPOINTER_ISA中有两个成员变量has_sidetable_rcextra_rc,当extra_rc的19位内存不够存储引用计数时,has_sidetable_rc的值就会变为1,那么此时引用计数会存储在SideTable中。

系统在底层构建了StripedMap结构:

StripedMap.png

SideTables可以理解为一个全局的 hash数组,里面存储了 SideTable类型的数据,其长度为64,就是里面有64个 SideTable
我们来看 StripedMap模板的定义:
iOS底层原理探索 — 内存管理(二)_第1张图片
StripedMap.png

通过官方解释可以知道, StripedMap 是一个以 void *keyTvaulehash 表。
SideTables中, T就是 SideTable类型的数据。
我们来看一下 SideTable的具体结构:

SideTable

我们截取SideTable部分源码进行分析:

iOS底层原理探索 — 内存管理(二)_第2张图片
SideTable.png

可以看到, SideTable的定义很简单,包括三个成员。接下来我们具体分析一下这三个成员。

spinlock_t slock

spinlock_t的底层是os_unfair_lock类型的锁,主要作用就是防止线程冲突。这里不做过多分析。

RefcountMap 引用计数表

RefcountMap 用来存储OC对象的引用计数。它实质上是一个以objc_objectkey的散列表,其vaule就是OC对象的引用计数。同时,当OC对象的引用计数变为0时,会自动将相关的信息从散列表中剔除。RefcountMap的定义如下:

RefcountMap.png

实质上是模板类型 objc::DenseMap。模板的三个类型参数 DisguisedPtrsize_ttrue 分别表示 DenseMapkey类型、 value类型、是否需要在 value == 0 的时候自动释放掉响应的 hash节点,这里是 true

我们在iOS底层原理探索 — 内存管理(一)一文中讲到,底层调用rootRetainCount方法,内部会做一系列的引用计数操作。读者可以结合上文讲述的RefcountMap 引用计数表回顾一下,本文就不在做赘述。

weak_table_t 弱引用表

弱引用表weak_table_t就是用来存储OC对象弱引用的相关信息。我们知道,SideTables一共只有64个节点,而在我们的APP中,一般都会不只有64个对象,因此,多个对象一定会重用同一个SideTable节点,也就是说,一个weak_table会存储多个对象的弱引用信息。因此在一个SideTable中,又会通过weak_table作为hash表再次分散存储每一个对象的弱引用信息。
我们来看一下weak_table_t的结构:

iOS底层原理探索 — 内存管理(二)_第3张图片
weak_table_t.png

通过官方注释可以看出, weak_table_t是一个全局弱引用表。 将对象id存储为 key,将 weak_entry_t结构存储为 value
通过源码可以看到,成员 weak_entries实质上是一个 hash数组,数组中存储 weak_entry_t类型的元素。我们重点来分析一下 weak_entry_t,我们先看一下源码:

// The address of a __weak variable.
// These pointers are stored disguised so memory analysis tools
// don't see lots of interior pointers from the weak table into objects.
typedef DisguisedPtr weak_referrer_t;

#if __LP64__
#define PTR_MINUS_2 62
#else
#define PTR_MINUS_2 30
#endif

/**
 * The internal structure stored in the weak references table. 
 * It maintains and stores
 * a hash set of weak references pointing to an object.
 * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
 * is instead a small inline array.
 */
#define WEAK_INLINE_COUNT 4

// out_of_line_ness field overlaps with the low two bits of inline_referrers[1].
// inline_referrers[1] is a DisguisedPtr of a pointer-aligned address.
// The low two bits of a pointer-aligned DisguisedPtr will always be 0b00
// (disguised nil or 0x80..00) or 0b11 (any other address).
// Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state.
#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
    DisguisedPtr referent; // 被弱引用的对象
    
    // 引用该对象的对象列表。 当引用个数小于4时,用inline_referrers数组。 引用个数大于4时,用动态数组weak_referrer_t *referrers
    union {
        struct {
            weak_referrer_t *referrers;                      // 弱引用该对象的对象列表的动态数组
            uintptr_t        out_of_line_ness : 2;           // 是否使用动态数组标记位
            uintptr_t        num_refs : PTR_MINUS_2;         // 动态数组中元素的个数
            uintptr_t        mask;                           // 用于hash确定动态数组index,值实际上是动态数组空间长度-1
            uintptr_t        max_hash_displacement;          // 最大的hash冲突次数
     
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }
    //赋值方法
    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }
    // 构造方法,里面初始化了静态数组
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

下面具体解释一下weak_entry_t中所有成员的含义:

DisguisedPtr referent:弱引用对象指针摘要。其实可以理解为弱引用对象的指针,只不过这里使用了摘要的形式存储。(所谓摘要,其实是把地址取负)。

union :引用该对象的对象列表,有两种形式:

  • 定长数组inline_referrers[WEAK_INLINE_COUNT]
  • 动态数组 referrers
    这两个数组是用来存储弱引用该对象的指针的指针的,同样也使用了指针摘要的形式存储。当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT时,使用定长数组。当超过WEAK_INLINE_COUNT时,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。

bool out_of_line(): 该方法用来判断当前的weak_entry_t是使用的定长数组还是动态数组。当返回true,此时使用的动态数组;当返回false,使用定长数组。

referrers数组里面存储的元素是weak_referrer_t类型,弱引用该对象的指针的指针,以指针摘要的形式存储的。

至此,我们对SideTables的结构有了大致的了解,下面一张图来概括一下:

iOS底层原理探索 — 内存管理(二)_第4张图片
SideTables结构示意图.png

更多技术知识请关注微信公众号
iOS进阶


iOS底层原理探索 — 内存管理(二)_第5张图片
iOS进阶.jpg

你可能感兴趣的:(iOS底层原理探索 — 内存管理(二))