内存分区与布局
栈区(Stack)
- 栈区是一块
连续的
内存空间,它的结构是从高地址往低地址
拉伸,遵循先进后出(FILO)
原则 - 栈区存储的是
局部变量
,函数
,方法
,参数
,指针
- 栈区的地址空间一般是以
0x7
开头 - 栈区由
编译器
自动分配内存和释放
堆区(Heap)
- 堆区是一块
不连续
的内存空间,它的结构是从低地址向高地址
扩展 - 堆区存储的是
对象
,需要开辟空间
的东西,栈区内存比较小,堆区比较大,所以在堆区开辟空间
全局区(静态区)
- 堆区地址空间一般以
0x6
开头 - 全局区又分为
.bss段
和.data段
,内存地址一般由0x1
开头:Bss
段:未初始化
的全局变量,静态变量,Data
段:已初始化
的全局变量,静态变量
- 静态区也被称之为
静态安全区
,因为在多个文件使用同一个静态变量,系统会各自生成一个相同初始值
且地址不同
的静态变量,这样在各自文件内使用就不会互相干扰,数据比较安全。
常量区(.rodata)
- 常量区的内存在
编译时
就已经确定,主要存放已经使用过的,且没有指向的字符串常量
(因为字符串常量可能在程序中多次被使用,所以在程序运行之前
就会提前分配内存)。常量区的常量在程序结束后,由系统释放
代码区(.text)
- 存储程序代码,在编译时加载到内存中,代码会被编译成
二进制的形式
进行存储
注意
-
- 我们可以从图中看出,栈底的内存为
0c0000000
,转成10进制
后等于3GB
,还有1G
的内存分配给了内核区
。
内核区
:主要进行消息处理
以及多线程的操作
- 我们可以从图中看出,栈底的内存为
-
- 代码区的地址是从
0x00400000
开始,怎么不是从0x0
开始? 因为0x0
是nil
,从nil
开始分配会出现问题,所以在代码区之前就有了保留区
保留区
:预留给系统处理nil
等
- 代码区的地址是从
内存管理方案
TaggedPointer(WWDC2020)
TaggedPointer
是字面意思就是标记的指针
,我们知道正常的指针指向的是内存,但对于有些类型例如NSNumber
,它不需要一般对象的cache_t
和method_list
等,它只是一个值完全可以存在指针中,这样也大大的优化访问速度与内存,于是就产生了TaggedPointer
。TaggedPointer
主要针对一些小对象类型,例如NSNUmber
,NSDate
,NSIndexPath
等,- 小对象将值存在本身的指针中,没有另外调用
Malloc
开辟或者free
释放内存, TaggedPointer
有3倍
内存空间的优化- 相比于
allocate/destory
,TaggedPointer
的速度要快106倍
Tagged Pointer
指针的值不再是地址了,⽽是真正的值。实际上它不再是⼀个对象了,只是⼀个披着对象⽪的普通变量⽽已。所以它的内存并不存储在堆中,也不需要malloc和freetaggedPointer
不会引起引用计数的变化,不会产生野指针导致崩溃
NONPOINTER_ISA
- 说到
taggedPointer
往往会联想到NONPOINTER_ISA
,isa
的种类和结构。NONPOINTER_ISA
表示是否对isa
指针开启指针优化: - 0:纯isa指针 - 1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等NONPOINTER_ISA
也是对存储进行了优化,使得isa
中的64位
得到充分的使用,其中的shiftcls
和taggedPointer
中的Payload
类似,用来存储有效数据
NONPOINTER_ISA
中的extra_rc
存储的是引用计数相关数据。
Retain、Release、retainCount、散列表
objc_object::rootRetain
// tryRetain: false, variant: Fast
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) { ... } //FastOrMsgSend类型的操作
if (slowpath(!oldisa.nonpointer)) { ... } // 非nonpointer操作
do {
transcribeToSideTable = false;
newisa = oldisa;
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())) { ... } //正在析构
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// 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) { // extra_rc 存满了
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;
}
do-while
循环代码先将oldisa
赋值给newisa
,如果newisa
是非nonpointer
类型,会根据判断走sidetable_tryRetain
或者sidetable_retain
方法,如果extra_rc
存满会走carry
判断进行extra_rc
存一半,循环外散列表存一半操作。先来看看sidetable_tryRetain
方法sidetable_tryRetain
:
bool
objc_object::sidetable_tryRetain()
{
//- 该方法的主要作用拿到当前对象的散列表中的引用计数表`SideTable`,然后判断是否在析构,判断没有存满的话就引用计数增加
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this]; // 拿到当前对象的散列表
bool result = true;
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE); // 拿到SideTable中的引用计数表`RefcountMap`
auto &refcnt = it.first->second; // 引用计数表中的size_t
if (it.second) {
// there was no entry
} else if (refcnt & SIDE_TABLE_DEALLOCATING) { // 正在析构
result = false;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) { // 没有存满
refcnt += SIDE_TABLE_RC_ONE; // 引用计数增加
/**
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0) //是否有过 weak 对象
#define SIDE_TABLE_DEALLOCATING (1UL<<1) //表示该对象是否正在析构
#define SIDE_TABLE_RC_ONE (1UL<<2) //从第三个 bit 开始才是存储引用计数数值的地方 ,第三个 bit 开始才是存储引用计数数值的地方
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1)) // 最高位,表示在最高位占满,用来判断是否存满
*/
}
return result;
}
sidetable_retain
id
objc_object::sidetable_retain(bool locked)
{
//该流程也是获取对象的散列表,然后进行引用计数增加。
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this]; // 获取对象的散列表
if (!locked) table.lock(); // 如果没有加锁,则调用散列表的互斥锁进行加锁
size_t& refcntStorage = table.refcnts[this]; // 获取当前引用计数
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { //判断是否存满
refcntStorage += SIDE_TABLE_RC_ONE; // 引用计数增加
}
table.unlock(); // 解锁
return (id)this;
}
后面再执行newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry)
给extra_rc
进行赋值,RC_ONE
是开始存储extra_rc
的位置。addc
函数的作用是将newisa.bits
和RC_ONE
相加并存入newisa.bits
当存满了carry
值就为1
。如果存满会将引用计数在extra_rc
中存储一半然后做相关的标记,然后调用sidetable_addExtraRC_nolock
方法
sidetable_addExtraRC_nolock
: 主要是从引用计数表获取引用计数后,将另一半引用计数存入相应的位置
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this]; // 获取对象的散列表
size_t& refcntStorage = table.refcnts[this]; // 从引用计数表获取引用计数
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true; // 如果存满直接返回
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); // delta_rc << SIDE_TABLE_RC_SHIFT 是从第二个位置开始存
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK); // 溢出的话就refcntStorage为可存储的最大值
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
retain流程图
objc_object::rootRelease
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false; // 先判断是不是小对象
bool sideTableLocked = false;
isa_t newisa, oldisa;
oldisa = LoadExclusive(&isa.bits);
if (variant == RRVariant::FastOrMsgSend) { ... }
if (slowpath(!oldisa.nonpointer)) { ... } 、、 是不是`nonpointer`
retry:
do {
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
return sidetable_release(sideTableLocked, performDealloc);
}
if (slowpath(newisa.isDeallocating())) { ... }
// 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;
...
return false;
underflow:
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
if (!sideTableLocked) { // 未加锁相关赋值后再进行 retry
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
auto borrow = sidetable_subExtraRC_nolock(RC_HALF); // 从散列表借一半
bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there
if (borrow.borrowed > 0) {
bool didTransitionToDeallocating = false;
newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
newisa.has_sidetable_rc = !emptySideTable;
bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
...
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
ClearExclusive(&isa.bits);
sidetable_addExtraRC_nolock(borrow.borrowed);
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Decrement successful after borrowing from side table.
if (emptySideTable)
sidetable_clearExtraRC_nolock();
if (!didTransitionToDeallocating) {
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
}
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
deallocate:
...
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
- 如果不是
nonpointer
就调用sidetable_release
方法进行引用计数减少操作,代码如下
objc_object::sidetable_release(bool locked, bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
if (!locked) table.lock();
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
auto &refcnt = it.first->second;
if (it.second) {
do_dealloc = true;
} else if (refcnt < SIDE_TABLE_DEALLOCATING) { // 如果是week对象
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
refcnt |= SIDE_TABLE_DEALLOCATING;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) { // 判断是否存满
refcnt -= SIDE_TABLE_RC_ONE; // 引用计数减1
}
table.unlock();
if (do_dealloc && performDealloc) {
// `do_dealloc`为`true`且`performDealloc`为`true`则发送`dealloc`消息.
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc)); // 调用dealloc
}
return do_dealloc;
}
- 再调用
subc
进行extra_rc--
,如果减多了调用sidetable_subExtraRC_nolock
向散列表借一半再对extra_rc
进行赋值,代码如下:
uintptr_t
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this]; // 获取散列表
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end() || it->second == 0) { // 散列表没有
// Side table retain count is zero. Can't borrow.
return { 0, 0 };
}
size_t oldRefcnt = it->second;
// isa-side bits should not be set here
ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT); // 借一半,剩余一半再存入
ASSERT(oldRefcnt > newRefcnt); // shouldn't underflow
it->second = newRefcnt;
return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT };
}
release流程图
dealloc源码分析
delloc->_objc_rootDealloc->rootDealloc
- 1.根据条件
判断是否有isa、cxx、关联对象、弱引用表、引用计数表
,如果没有
,则直接free释放内存
- 2.如果
有
,则进入object_dispose方法
object_dispose
object_destructInstance
clearDeallocating
clearDeallocating_slow
通过上面可以看到,object_dispose方法的目的有一下几个
- 1.销毁实例,主要有以下操作
- 1.调用c++析构函数
- 2.删除关联引用
- 3.释放散列表
- 4.清空弱引用表
- 2.free释放内存
retainCount
retainCount
主要调用流程如下:retainCount
->_objc_rootRetainCount
->rootRetainCount
,核心代码如下:
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
if (bits.nonpointer) {
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) { // 如果有散列表,retainCount值为 extra_rc 和引用计数表里的值相加
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount(); // 如果是非`nonpointer`,则直接取散列表
}
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
if (bits.nonpointer) {
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) { // 如果有散列表,retainCount值为 extra_rc 和引用计数表里的值相加
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount(); // 如果是非`nonpointer`,则直接取散列表
}
- 如果是
isTaggedPointer
直接返回,如果是nonpointer
类型,则取extra_rc
,如果在散列表中存有,则取散列表中的值和extra_rc
的和 - 无论是
sidetable_getExtraRC_nolock
还是sidetable_retainCount
方法里面都有引用计数值 >> SIDE_TABLE_RC_SHIFT
的操作,主要是因为从存的时候从第二位开始存,所以计算retainCount
时需要右移SIDE_TABLE_RC_SHIFT
。
散列表
散列表SideTable
是一个结构体,主要由互斥锁
,RefcountMap
引用计数表,和weak_table_t
弱引用表三个成员变量组成,还有相关的析构函数和加锁方法: