iOS内存管理(一)、内存分区和引用计数
iOS内存管理(二)alloc、retain、release、dealloc
一. alloc源码分析
+ (id)alloc {
return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
//我们是OC2,所以会进入这里
//关于slowpath和fastpath在下方会有解释,大致意思就是告诉编译器,
//slowpath这里执行可能更小,而fastpath执行可能更大
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
//再调用_objc_rootAllocWithZone方法去实际的分配内存
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
NEVER_INLINE id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
//实际创建对象分配内存的方法
static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
//确定应该分配的内存
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
//核心:这里开辟内存
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
//初始化isa
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
//确定分配内存的大小
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
/*
这里可以看到一个OC对象初始化至少为16个字节
iOS的指定对齐为8,所以OC对象的内存大小都是8的倍数
*/
if (size < 16) size = 16;
return size;
}
综上所述,我们可以得出如下结论:
-
+ (id)alloc
方法调用_objc_rootAlloc
方法 -
_objc_rootAlloc
调用callAlloc
方法,callAlloc
调用_objc_rootAllocWithZone
,_objc_rootAllocWithZone
调用_class_createInstanceFromZone
方法,这里实际完成对象的内存开辟 - OC对象的内存最小为16个字节(也就是说,一个NSObject对象,至少有16个字节)
__builtin_expect
我们对callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
函数中出现的slowpath
作出一点说明,我们可以看到slowpath
的定义
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
__builtin
这个指令是gcc引入的,作用是允许程序员将最有可能执行的分支告诉编译器。这个指令的写法为:__builtin_expect(EXP, N)
意思是:EXP==N的概率很大。
一般的使用方法是将__builtin_expect
指令封装为likely
和unlikely
宏。这两个宏的写法如下.
#define likely(x) __builtin_expect(!!(x), 1) //x很可能为真
#define unlikely(x) __builtin_expect(!!(x), 0) //x很可能为假
内核中的 likely()
与 unlikely()
,首先要明确:
if(likely(value)) //等价于 if(value)
if(unlikely(value)) //也等价于 if(value)
__builtin_expect()
是 GCC (version >= 2.96)
提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
__builtin_expect((x),1)
表示 x 的值为真的可能性更大;
__builtin_expect((x),0)
表示 x 的值为假的可能性更大。
也就是说,使用likely()
,执行 if 后面的语句的机会更大,使用 unlikely()
,执行 else 后面的语句的机会更大。通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降。
isa的初始化
我们再看看isa
的初始化
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
isa
的初始化是在_class_createInstanceFromZone
中,在完成对象内存的分配后,对isa
进行初始化,而且从上面可以看出,isa初始化的时候,并没有存储1个引用计数,这就是为什么之前retainCount
的源码中从数据中取出引用计数要加1的原因了
init的源码简介
+ (id)init {
return (id)self;
}
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
return obj;
}
从源码中可以看到,无论是哪种形式的init初始化,都是直接返回自身,并没有进行任何的操作
new的源码
我们再来看看new
的源码
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
可以看出,new
实际上也是先调用callAlloc
完成内存分配,然后再调用init
,所以它等同于alloc+init
的调用
二. retain源码分析
//类方法的retain直接返回自身
+ (id)retain {
return (id)self;
}
//实例方法的retain
- (id)retain {
return _objc_rootRetain(self);
}
NEVER_INLINE id
_objc_rootRetain(id obj)
{
ASSERT(obj);
return obj->rootRetain();
}
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
#define RC_ONE (1ULL<<56)
#define RC_HALF (1ULL<<7)
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
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
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
//extra_rc中加1,carry判断是否溢出,RC_ONE是(1ULL<<56),正好是extra_rc的低位
//从这里我们也可以看出,extra-rc最多能存储8位的引用计数值,也就是最多(2^8-1 = 255)位引用计数
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//如果上溢出
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;
//如果上溢出,那么把一半(2^7=128)留在extra_rc中,还有一半(2^7=128)存到sidetable中
newisa.extra_rc = RC_HALF;
//把有sidetable的标志位置为true
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.
//将一半(2^7=128)存到sidetable中
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
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);
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
如源码中所见,我们可以得出如下结论:
- 类方法的
retain
直接返回自身 -
taggedpointer
对象的retain
直接返回自身 - 实例方法的
retain
调用步骤为:- (id)retain
->_objc_rootRetain(self)
->objc_object::rootRetain()
->objc_object::rootRetain(false, false)
,关于引用计数的存储操作在objc_object::rootRetain
中实现 - isa中最多存储(2^8-1 = 255)个引用计数值
引用计数的存储步骤:
- 首先取出
isa_t.bits
,和RC_ONE
(就是1)相加,如果没有上溢出,返回 - 如果发生了上溢出,则将
isa
中的引用计数值拿出一半(RC_HALF = 2^7 = 128)存储到sidetable中,一半留在isa
中存储,并将isa
位域中的has_sidetable_rc
位置为true
三. release源码分析
//类方法的release什么也不做
+ (oneway void)release {
}
//实例方法中调用_objc_rootRelease
- (oneway void)release {
_objc_rootRelease(self);
}
NEVER_INLINE void _objc_rootRelease(id obj)
{
//判空
ASSERT(obj);
obj->rootRelease();
}
ALWAYS_INLINE bool objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
//TaggedPointer直接返回
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
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;
//将extra_rc减一
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
//发现下溢出,跳转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;
//如果sidetable存在
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.
//从sidetable中借一半(2^7=128),用于之后放到extra_rc中
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,然后存到isa的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.
}
}
// Really deallocate.
//判断是否正在析构
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
//正在析构,返回一个错误
return overrelease_error();
// does not actually return
}
//extra_rc产生了下溢出,而没有sidetable,引用计数为0了,就该析构了
//将析构的标记置为true
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
//调用的时候performDealloc传过来就是true
if (performDealloc) {
//主动调起析构函数dealloc
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
从release
的源码我们可以得出如下结构
- 类方法的
release
什么也没做 -
taggedpointer
对象的release
直接返回自身 - 实例方法的
release
调用步骤为:- (oneway void)release
->_objc_rootRelease(self)
->objc_object::rootRelease()
->objc_object::rootRelease(true, false)
objc_object::rootRelease
函数中操作步骤:
- 首先在
retry
标签中对isa中的extra_rc
减一,如果没有下溢出,返回 - 如果发生了下溢出(即
extra_rc
之前为0,减去1之后为-1),则跳转到underflow
标签中 - 在
underflow
标签中先根据has_sidetable_rc
判断是否有散列表,如果有散列表则在散列表中取出RC_HALF(2^7=128)
减去1,再存入isa的extra_rc
中,如果散列表有是空的,则跳到第4步 - 如果没有散列表,或者散列表中存储的引用计数为0,则将isa的
deallocating
析构中标志标记为true,接着发送消息调用析构方法dealloc
问题:为什么每次都RC_HALF
的取出呢?
答:(1)128个,则不用每次调用哈希算法,频繁操作散列表;(2)1<<7
正好是一个字节的长度,存取方便(刚好占用一条数据总线)
四. dealloc源码分析
//类方法什么也不做
+ (void)dealloc {
}
//实例方法调用_objc_rootDealloc
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
//如果不是优化后的isa,而且没有被弱引用,没有关联对象,没有C++的析构函数,并且没有sidetable,直接释放
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
object_dispose((id)this);
}
}
id object_dispose(id obj)
{
if (!obj) return nil;
//先调用objc_destructInstance析构所有的成员
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);
//移除weak
obj->clearDeallocating();
}
return obj;
}
//移除对象的关联对象
void _object_remove_assocations(id object)
{
//关联哈希表
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
associations.erase(i);
}
}
// release everything (outside of the lock).
for (auto &i: refs) {
i.second.releaseHeldValue();
}
}
//移除weak
inline void objc_object::clearDeallocating()
{
sidetable_clearDeallocating();
}
//移除sidetable中的weak
void objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
//找到到调用weak_clear_no_lock移除weak_table中的weak
weak_clear_no_lock(&table.weak_table, (id)this);
}
table.refcnts.erase(it);
}
table.unlock();
}
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
如源码中可见,我们可以得出如下结论:
- 类方法的
dealloc
什么也不做 - 实例方法的
dealloc
调用步骤为:dealloc
->_objc_rootDealloc
->objc_object::rootDealloc()
- 在
objc_object::rootDealloc()
方法中,如果不是优化后的isa
指针(nonpointer
为0),而且没有被弱引用过,且没有关联对象,没有C++
的析构函数,并且没有has_sidetable_rc
为0,直接调用free
释放对象,否则调用object_dispose
-
object_dispose
中,首先调用objc_destructInstance(obj)
析构所有成员,然后再调用free
释放对象
objc_destructInstance(obj)
的调用步骤:
- 如果有
C++
的析构函数,调用object_cxxDestruct(obj)
- 如果有关联对象,移除:
_object_remove_assocations(obj)
- 调用
obj->clearDeallocating()
移除weak
弱引用
综上所述,其实dealloc
就是先析构所有的成员(包括关联对象、弱引用、C++析构方法),然后直接调用free
释放对象