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_rc
和extra_rc
,当extra_rc
的19位内存不够存储引用计数时,has_sidetable_rc
的值就会变为1,那么此时引用计数会存储在SideTable
中。
系统在底层构建了StripedMap
结构:
SideTables
可以理解为一个全局的hash
数组,里面存储了SideTable
类型的数据,其长度为64,就是里面有64个SideTable
。我们来看
StripedMap
模板的定义:StripedMap<T>.png
通过官方解释可以知道,
StripedMap
是一个以void *
为key
, T
为vaule
的hash
表。在
SideTables
中,T
就是SideTable
类型的数据。我们来看一下
SideTable
的具体结构:
SideTable
我们截取SideTable
部分源码进行分析:
可以看到,
SideTable
的定义很简单,包括三个成员。接下来我们具体分析一下这三个成员。
spinlock_t slock
spinlock_t
的底层是os_unfair_lock
类型的锁,主要作用就是防止线程冲突。这里不做过多分析。
RefcountMap 引用计数表
RefcountMap
用来存储OC对象的引用计数。它实质上是一个以objc_object
为key
的散列表,其vaule
就是OC对象的引用计数。同时,当OC对象的引用计数变为0时,会自动将相关的信息从散列表中剔除。RefcountMap
的定义如下:
实质上是模板类型
objc::DenseMap
。模板的三个类型参数DisguisedPtr<objc_object>
、size_t
、true
分别表示DenseMap
的 key
类型、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
的结构:
通过官方注释可以看出,
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<objc_object *> 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<objc_object> 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<objc_object> 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进阶
iOS进阶.jpg