前言
iOS-内存管理分析(上)一文我们分析了内存的五大区,taggedPointer,retain,release的底层分析, 这篇文章继续分析内存管理的其它相关知识。
1 散列表结构分析
散列表到底是什么东西,它有什么作用,我们现在来分析下。
散列表其实就是哈希表。只是对当前表的名字
首先我们在objc的源码中搜索一下rootRetain,找到如下代码
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) {
// These checks are only meaningful for objc_retain()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa.bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
return swiftRetain.load(memory_order_relaxed)((id)this);
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
}
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa.bits);
return (id)this;
}
}
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())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
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) {
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;
}
- sidetable_tryRetain*我们看下这个函数的源码,所示
bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
// NO SPINLOCK HERE
// _objc_rootTryRetain() is called exclusively by _objc_loadWeak(),
// which already acquired the lock on our behalf.
// fixme can't do this efficiently with os_lock_handoff_s
// if (table.slock == 0) {
// _objc_fatal("Do not call -_tryRetain.");
// }
bool result = true;
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
auto &refcnt = it.first->second;
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;
}
return result;
}
这里调用了SideTable& table = SideTables()[this];这个SideTables函数,散列表是有多张的(真机8张表,模拟器 6张表)基本结构如下
template<>
void SideTable::unlockTwo
(SideTable *, SideTable *lock2)
{
lock2->unlock();
}
static objc::ExplicitInit> SideTablesMap;
static StripedMap& SideTables() {
return SideTablesMap.get();
}
// anonymous namespace
};
void SideTableLockAll() {
SideTables().lockAll();
}
void SideTableUnlockAll() {
SideTables().unlockAll();
}
void SideTableForceResetAll() {
SideTables().forceResetAll();
}
void SideTableDefineLockOrder() {
SideTables().defineLockOrder();
}
void SideTableLocksPrecedeLock(const void *newlock) {
SideTables().precedeLock(newlock);
}
void SideTableLocksSucceedLock(const void *oldlock) {
SideTables().succeedLock(oldlock);
}
void SideTableLocksPrecedeLocks(StripedMap& newlocks) {
int i = 0;
const void *newlock;
while ((newlock = newlocks.getLock(i++))) {
SideTables().precedeLock(newlock);
}
}
void SideTableLocksSucceedLocks(StripedMap& oldlocks) {
int i = 0;
const void *oldlock;
while ((oldlock = oldlocks.getLock(i++))) {
SideTables().succeedLock(oldlock);
}
}
为什么散列表是多张表,而不是一张表?
我们往下分析,我们看下SideTable是什么,找到其源码。
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template
static void lockTwo(SideTable *lock1, SideTable *lock2);
template
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
spinlock_t是把锁
RefcountMap是引用表计数表,我们上篇文章提到的extra_rc就存在这里
weak_table_t是弱引用计数表,比如被__weak修饰的
如果在系统中共用一张表的话,就会大大影响性能(每次需要开锁,解锁),多张表的可以即时回收内存(一张表的数据都置空,这张表就可以回收)
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;
}
这里获取对象所在的表,并根据这张表获取到对象所在的存储空间,对其进行++操作。
sidetable_release源码
uintptr_t
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) {
// 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;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}
2 引用计数分析
NSObject *objc = [[NSObject alloc] init];
__weak typeof (id) weakObjc = objc;
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
我们经常会有这样的写法,通过__weak修饰变量,这个weakObjc会被加入弱引用表中。
我们在源码中搜下retainCount找到以下源码
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) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
在这里拿到isa的bits,判断如果是nonpointer,这里不会执行
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
因为extra_rc还未存满,所以不会进入散列表,CFGetRetainCount这个函数打印1是因为在isa初始化的默认赋值1。
我们调整下代码,如下
NSObject *objc = [[NSObject alloc] init];
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
__weak typeof (id) weakObjc = objc;
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),objc);
这时候运行项目会出现什么情况,我们试下,如图
打印结果1,1,2。
这是为什么?
我们加入弱引用表是不影响引用计数的,为什么打印weakObjc是2呢?我们要想知道这些,我们就得分析弱引用表。
3 弱引用表
我们要分析__weak的原理是什么,就要探索一下,在我们刚才的代码上,先运行项目,如图
这里发现进到源码中了,在objc_initWeak这个函数中,因为我们是通过源码编译分析的,如果不是通过源码分析,也可以通过汇编一步一步的分析流程。
我们在源码中搜索objc_initWeak这个函数,如下
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak
(location, (objc_object*)newObj);
}
这也说明了,只要使__weak修饰,就会进入到objc_initWeak这个函数,也就是说__weak这个标示符与objc_initWeak有绑定关系, 我们可以在llvm中找到相应的绑定关系,如图
这里可以看出__weak与OCL_Weak的对应关系,OCL_Weak就是objc_initWeak,如图
GetFunctionClass会返回要调用的函数,如果是__weak类型就会调用objc_initWeak这个函数。
objc_initWeak调用storeWeak传的是(objc_object)newObj,而objc_destroyWeak*释放函数这里传的是nil,
storeWeak
(location, (objc_object*)newObj);
location是weakSelf指针地址,(objc_object)newObj就是要绑定的对象,我们接着看下storeWeak*的源码,如下
template
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo(oldTable, newTable);
class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (!newObj->isTaggedPointerOrNil()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo(oldTable, newTable);
// This must be called without the locks held, as it can invoke
// arbitrary code. In particular, even if _setWeaklyReferenced
// is not implemented, resolveInstanceMethod: may be, and may
// call back into the weak reference machinery.
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
这段代码
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
判断在弱引用表查找,有没有这个对象,因为第一次进来,肯定没有,所以oldTable为nil。
接着
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
这里判断能不能找到原来的对象,第一次进来,肯定是可进来的,我们调试下,如图
然后再执行
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
如果是旧的,移除。
如果是新的就注册进来:
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo(oldTable, newTable);
class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
如果新的对象进来,执行这里的代码。
再接着
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (!newObj->isTaggedPointerOrNil()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
weak_register_no_lock调用这个函数,
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (referent->isTaggedPointerOrNil()) return referent_id;
// ensure that the referenced object is viable
if (deallocatingOptions == ReturnNilIfDeallocating ||
deallocatingOptions == CrashIfDeallocating) {
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
// Use lookUpImpOrForward so we can avoid the assert in
// class_getInstanceMethod, since we intentionally make this
// callout with the lock held.
auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
referent->getIsa());
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
}
if (deallocating) {
if (deallocatingOptions == CrashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
}
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
在这里
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
在这里添加,在weak_entry_for_referent如果找到referent(修饰的对象)存储,直接调用append_referrer往里面添加。
如果没有找到,weak_entry_t new_entry(referent, referrer);(对象的指针地址,对象)创建新的实体。
weak_grow_maybe扩容表的处理。
weak_entry_insert把实体添加到弱引用表中。
刚才我们有介绍了referent存放的对象的指针地址,那么实际是什么?我们来调试下,我们先看张图
这时可以看出referent就是objc,所以referent就是我们的对象
但是referrer地址不同,我们来分析下weak_entry_t结构。
struct weak_entry_t {
DisguisedPtr referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
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;
}
}
};
referent存了很多referrer。
如果是weak修饰的属性, referent还是我们的对象,referrers存放我们的属性。
weak大致流程
- weak是存在弱引用表中(散列表sideTable)
- 通过sideTable查找到weakTable弱引用表
- 创建一个weak_entry_t
- 把referent(对象的指针地址,如weakSelf)加入到weak_entry_t的数组inline_referrers
- 把weak_table扩容一下
- 把new_entry加入到weak_table中
函数流程 - objc_initWeak
- storeWeak
- weak_unregister_no_lock
- weak_register_no_lock ----->weak_entry_for_referent ----->weak_entry_for_referent -----> new_referrers[i]=entry->inline_referrer[i]
- weak_entry_t->inline_referrer[i]
- weak_grow_maybe
- weak_entry_insert
4 关于弱引用的引用计数
NSObject *objc = [[NSObject alloc] init];
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
__weak typeof (id) weakObjc = objc;
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),objc);
我们看下执行结果,如图
这里
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
输出为1
这里
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)),objc);
输出也是1
这里
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),objc);
输出为2。
这是为什么?
NSLog(@"%d----%@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)),objc);
这里访问的是weakObjc。
objc指向这块内存,reatinCount=1,但是weakObjc是弱引用,不持有这个对象,为什么会是2呢,因为弱引用表把我们的这个对象复制了一份,加入引用计数表中,copy了这份对象跟复制的那份是指向同一块内存,我们获取weakObjc的引用计数,是获取copy那份的引用计数
我们断点,运行项目,如图
调用objc_loadWeak函数,接着,我们在源码搜下这个函数,如下
id
objc_loadWeak(id *location)
{
if (!*location) return nil;
return objc_autorelease(objc_loadWeakRetained(location));
}
这里调用了objc_loadWeakRetained这个函数,我们搜下objc_loadWeakRetained这个函数
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
obj = *location;
if (obj->isTaggedPointerOrNil()) return obj;
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
// Slow case. We must check for +initialize and call it outside
// the lock if necessary in order to avoid deadlocks.
// Use lookUpImpOrForward so we can avoid the assert in
// class_getInstanceMethod, since we intentionally make this
// callout with the lock held.
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
result = nil;
}
}
else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}
table->unlock();
return result;
}
我们在这个函数,再断点进去,看下
如图
这里的location就是weakSelf,这里就是获取weakSelf所指向的内存空间。
经过调试发现,是在
if (! obj->rootTryRetain()) {
result = nil;
}
函数调用rootRetain这个函数,这里会执+1操作,如图
这里就有一个疑问了,如果一直执行rootRetain会一直+1操作,但是发现不是,weakObjc的引用计数一直2 ,为什么?
因为id obj;是临时变量,出了函数作用域就不存在了,因为会发送release消息。
总结
本篇文章, 我们分析散列表,计用计数,弱引用计数。本篇文章有遗漏的地方,烦请批评指正。