内存分区与布局
栈区(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弱引用表三个成员变量组成,还有相关的析构函数和加锁方法: