内存布局
在前面文章中介绍内存的五大区域,其实除了内存区
还有内核区
和保留区
-
内存区
: 系统用来进行内核处理操作的区域 -
保留区
:预留给系统处理nil等情况
全局变量和局部变量有何区别
-
全局变量
:存储在全局区(bss+data),占用静态的存储单元 -
局部变量
:存储在栈区
,只会在对应的函数调用时才会动态分配内存
block可以修改全局变量、全局静态变量、局部变量、局部静态变量
可以修改
全局变量、全局静态变量
,因为他们是全局的
,作用域广可以修改
局部静态变量
,block通过指针拷贝
的形式捕获局部静态变量,然后成为__main_block_impl_0
结构体的变量不可以修改
局部变量
,block通过值拷贝
的形式捕获局部变量,然后成为__main_block_impl_0
结构体的变量,在ARC环境下,使用
__block
修饰外部变量并在block中修改就会触发copy
,block从栈区copy堆区
变成堆区block
在ARC环境下,block中引用
id类型
数据时,通过__blcok修饰
,会在底层修改结构体__Block_byref_a_0
,将内部的forwarding指针
指向copy
后的地址
MRC和ARC
iOS中的内存管理方案分为:MRC
(手动内存管理)和ARC
(自动内存管理)
MRC
系统通过对象的引用计数来判断对象是否需要销毁,必须遵守谁创建、谁释放,谁引用、谁管理
- 对象
创建
时引用计数+1
- 当对象被
其他指针引用
时,需要手动调用[objc retain]
使对象的引用计数+1
- 当指针变量不需要使用对象时,需要调用
[objc release]
释放对象使引用计数-1
- 当对象的引用计数为
0
时,系统才会销毁
对象
ARC
从iOS5引入的自动管理机制,其规则与MRC一致,区别在于不需要手动retain、release,编译器在适当位置插入retain、release
Tagged Pointer 小对象
-
进入objc源码,搜索
setProperty -> reallySetProperty
,其中是对新值的retain、旧值的release
进入
objc_retain、objc_release
源码,如果是小对象,不会进行retain和release,直接返回
//****************objc_retain****************
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj)
{
if (!obj) return obj;
//判断是否是小对象,如果是,则直接返回对象
if (obj->isTaggedPointer()) return obj;
//如果不是小对象,则retain
return obj->retain();
}
//****************objc_release****************
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj)
{
if (!obj) return;
//如果是小对象,则直接返回
if (obj->isTaggedPointer()) return;
//如果不是小对象,则release
return obj->release();
}
- 对于
Tagged Pointer
指针中包含了指针+值
,进入_read_images -> initializeTaggedPointerObfuscator
源码中
static void
initializeTaggedPointerObfuscator(void)
{
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
DisableTaggedPointerObfuscation) {
objc_debug_taggedpointer_obfuscator = 0;
}
//在iOS14之后,对小对象进行了混淆,通过与操作+_OBJC_TAG_MASK混淆
else {
// Pull random data into the variable, then shift away all non-payload bits.
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
}
}
- 搜索
objc_debug_taggedpointer_obfuscator
,可以看到taggedPointer的编码与解码
,
//编码
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;
- 源码中查找
_objc_isTaggedPointer
,通过保留最高的值(64位)
来判断是否等于_OBJC_TAG_MASK
(2^63)来判断是否是小对象
,判断第64位上是否为1(taggedpointer指针地址即表示指针地址,也表示值) -
0xa
转化成二进制1 010
,(64位为1,63-61表示小对象的类型为 2),表示NSString -
0xb
转化成二进制1 011
,(64位为1,63-61表示小对象的类型为 3),表示NSNumber,如果NSNumber的值是-1
,则地址中的值用补码
表示
通过_objc_makeTaggedPointer
参数的tag类型objc_tag_index_t
进入枚举
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
//等价于 ptr & 1左移63,即2^63,相当于除了64位,其他位都为0,即只是保留了最高位的值
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
Tagged Pointer 总结
Tagged Pointer
小对象类型是用来存储NSNumber、NSDate、小NSString
,小对象指针不仅是简单的地址,还包含了真正的值
,可以直接进行读取,占用空间小,节约内存Tagged Pointer
小对象不会进入retain、release
,意味着不需要ARC管理,可以直接被系统自主释放和回收Tagged Pointer
小对象存储在常量区
,不需要malloc和free,可以直接读取Tagged Pointer
的64位地址中,64位
用来判断是否是小对象,63-61
位判断小对象类型,后4位
用于系统处理,中间56位
用来存储值
Nonpointer_isa &
Nonpointer_isa
:非指针类型的isa,用来优化64位地址,可以查看我前文isa和类的关联
SideTables
当引用计数存储到一定值时,并不会再存储到Nonpointer_isa
的位域的extra_rc
,而是会存储到SideTables
散列表中。在散列表中主要有两个表,分别是引用计数表、弱引用表
,同一时间,真机中散列表最多只能有8张
- 散列表的定义
struct SideTable {
spinlock_t slock;//开/解锁
RefcountMap refcnts;//引用计数表
weak_table_t weak_table;//弱引用表
....
}
为什么在用散列表,而不用数组、链表?
-
数组
:读取快,存储慢,因为有下标 -
链表
:读储慢,存取快,因为有节点 -
散列表
:本质是一个哈希表,增删改查都快,通过哈希函数获取哈希下标,存有父子节点
retain原理
进入源码objc_retain -> retain -> rootRetain
- 【第一步】判断是否是
Nonpointer_isa
- 【第二步】操作引用计数
- 如果不是
Nonpointer_isa
,则直接操作SideTables
散列表 - 判断
是否正在释放
,如果正在释放就执行dealloc - 执行
ectra_rc+1
,引用计数+1,并给一个引用计数的状态标识carry
来判断ectra_ra
是否满了 - 如果
carry
标记状态表示ectra_rc引用计数满了
,此时开始操作散列表
,即将ectra_rc
存储的一半拿出存到散列表
,
- 如果不是
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
//为什么有isa?因为需要对引用计数+1,即retain+1,而引用计数存储在isa的bits中,需要进行新旧isa的替换
isa_t oldisa;
isa_t newisa;
//重点
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//判断是否为nonpointer isa
if (slowpath(!newisa.nonpointer)) {
//如果不是 nonpointer isa,直接操作散列表sidetable
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
//dealloc源码
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
//执行引用计数+1操作,即对bits中的 1ULL<<45(arm64) 即extra_rc,用于该对象存储引用计数值
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//判断extra_rc是否满了,carry是标识符
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
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;
//如果extra_rc满了,则直接将满状态的一半拿出来存到extra_rc
newisa.extra_rc = RC_HALF;
//给一个标识符为YES,表示需要存储到散列表
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
//将另一半存在散列表的rc_half中,即满状态下是8位,一半就是1左移7位,即除以2
//这么操作的目的在于提高性能,因为如果都存在散列表中,当需要release-1时,需要去访问散列表,每次都需要开解锁,比较消耗性能。extra_rc存储一半的话,可以直接操作extra_rc即可,不需要操作散列表。性能会提高很多
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
retain总结
-
retain
会先判断是否是Nonpointer isa
,如果不是,直接操作散列+1 - 如果是
Nonpointer isa
,判断是否正在释放
,如果是,则执行dealloc
- 如果不是正在释放,则
引用计数+1
,extra_rc
在真机上只有8位
用于存储引用计数的值,当存储满了时,将一半(即2^7)存储在散列表中。另一半还是存储在extra_rc中,用于常规的引用计数的+1或者-1操作,然后再返回
release原理
通过setProperty -> reallySetProperty -> objc_release -> release -> rootRelease -> rootRelease
进入rootRelease源码,其中与retain相反
- 判断是否是
Nonpointer isa
,如果不是,操作散列表-1
- 如果是,对
extra_rc
中的引用计数-1,并将此时的extra_rc状态存储到carry
- 如果
carry == 0
,执行underflow
- 执行
underflow
- 判断散列表是否存储了一半引用计数
- 如果是,从散列表中取出一半引用计数,进行-1,然后存储到extra_rc中
- 如果不是,直接
dealloc
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//判断是否是Nonpointer isa
if (slowpath(!newisa.nonpointer)) {
//如果不是,则直接操作散列表-1
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
//进行引用计数-1操作,即extra_rc-1
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
//如果此时extra_rc的值为0了,则走到underflow
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
//判断散列表中是否存储了一半的引用计数
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
// Try to remove some retain counts from the side table.
//从散列表中取出存储的一半引用计数
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
//进行-1操作,然后存储到extra_rc中
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
//此时extra_rc中值为0,散列表中也是空的,则直接进行析构,即自动触发dealloc流程
// Really deallocate.
//触发dealloc的时机
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
//发送一个dealloc消息
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
dealloc 原理
进入源码dealloc -> _objc_rootDealloc -> rootDealloc
- 判断
是否是小对象
,如果是直接返回 - 判断
是否有isa. nonpointer、cxx、关联对象、弱引用表、引用计数表
,- 如果没有,直接free是否内存
- 如果有,执行
object_dispose
inline void
objc_object::rootDealloc()
{
//对象要释放,需要做哪些事情?
//1、isa - cxx - 关联对象 - 弱引用表 - 引用计数表
//2、free
if (isTaggedPointer()) return; // fixme necessary?
//如果没有这些,则直接free
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
//如果有
object_dispose((id)this);
}
}
- 进入
object_dispose
源码- 销毁实例
- 调用c++析构函数
- 删除关联引用
- 是否散列表
- 清空弱引用表
- 销毁实例
- free释放内存
id
object_dispose(id obj)
{
if (!obj) return nil;
//销毁实例而不会释放内存
objc_destructInstance(obj);
//释放内存
free(obj);
return nil;
}
👇
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
//调用C ++析构函数
if (cxx) object_cxxDestruct(obj);
//删除关联引用
if (assoc) _object_remove_assocations(obj);
//释放
obj->clearDeallocating();
}
return obj;
}
👇
inline void
objc_object::clearDeallocating()
{
//判断是否为nonpointer isa
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
//如果不是,则直接释放散列表
sidetable_clearDeallocating();
}
//如果是,清空弱引用表 + 散列表
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
👇
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
//清空弱引用表
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
//清空引用计数
table.refcnts.erase(this);
}
table.unlock();
}
retainCount原理
进入retainCount -> _objc_rootRetainCount -> rootRetainCount
源码中
- (NSUInteger)retainCount {
return _objc_rootRetainCount(self);
}
👇
uintptr_t
_objc_rootRetainCount(id obj)
{
ASSERT(obj);
return obj->rootRetainCount();
}
👇
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
//如果是nonpointer isa,才有引用计数的下层处理
if (bits.nonpointer) {
//alloc创建的对象引用计数为0,包括sideTable,所以对于alloc来说,是 0+1=1,这也是为什么通过retaincount获取的引用计数为1的原因
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
//如果不是,则正常返回
sidetable_unlock();
return sidetable_retainCount();
}
- 源码断点调试,查看此时的
extra_rc
的值
retainCount总结
- alloc创建的对象没有
retain和release
- alloc创建的对象的
引用计数==0
,会在编译时期,自动+1,所以读取时引用计数=1