内存布局
了解程序内存布局请点击程序的内存布局以及栈、堆原理;
内存管理方案
在学习内存管理之前先思考一下这几个问题:
1、对象的引用计数存放在什么地方?怎么读写的?
2、对象释放的时候怎么处理弱引用表、关联对象的?弱引用为什么可以在对象时自动置为nil?
3、什么是SideTable?它跟引用计数表和弱引用表是什么关系?
4、自动释放池是如何管理内存的?什么时候创建?什么时候释放对象?
MRC
MRC(Manual Reference Counting)翻译出来就是手动引用计数。在Xcode4之前,只能通过MRC机制管理内存,MRC要求开发人员手动管理内存,维护OC对象的引用计数。也就是说,在需要方手动调用retain、release等内存管理相关操作。
ARC
ARC(Automatic Reference Counting),翻译出来就是自动引用计数。这是相对于MRC的改进,本身内存管理还是通过引用计数机制的,只不过是不需要开发人员手动维护,程序在编译时期会在适当的地方自动插入相关的retain、release等代码,达到自动管理引用计数的目的。
Tagged Pointer小对象
Tagged Pointer计数是将一些小对象诸如NSString、NSNumber、NSDate等类型转成Tagged Pointer对象,它们的值直接存储在对象指针中。不需要开辟堆内存,也就是不需要malloc和free,也不需要引用计数retain、release等操作(点击了解更多关于Tagged Pointer小对象的内容)。
引用计数机制的底层原理
不管是MRC还是ARC,都是通过引用计数来管理内存的。那么什么是引用计数?它是如何通过引用计数来管理内存的呢?引用计数是计算机编程语言中的一种内存管理技术,是指将对象的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。在iOS中引用又分为强引用(strong)和弱引用(weak),强引用是引用计数会增加,弱引用则不会。iOS中常见的引用计数相关的操作有:
- alloc对象创建时(老版本源码的alloc是不会初始化引用计数的,这里版本是objc4-818.2);
retain(包括strong修饰的属性),引用计数加1;
release,引用计数减1;
autorelease 自动释放,对象指针会被添加到释放池中,在自动释放池drain时释放;
retainCount,获取引用计数个数。
接下来通过源码对各个操作进行解析。
alloc时初始化引用计数
alloc是对象创建时会调用initIsa方法初始化isa(点击了解对象创建过程),初始化isa的时候会初始化引用计数为1(点击了解更多关于isa的信息):
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
const char *mangledName = cls->mangledName();
if (strcmp("MyObject", mangledName) == 0) {
if(!cls->isMetaClass()){//避免元类的影响
printf("我来了 MyObject");//定位要调试的类
}
}
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#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
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
初始化引用计数:
newisa.extra_rc = 1;
retain底层源码解析
对象的retain操作最终是通过方法rootRetain来实现的。rootRetain主要做了如下几件事情:
1、读取出isa指针信息(点击了解更多关于isa的信息)。
oldisa = LoadExclusive(&isa.bits);
2、判断对象是否有自己的默认实现的retain方法。
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));
}
}
如果有的话就走自己方法。
3、判断是否是nonpointer对象,如果是nonpointer对象的话不需要引用计数管理,比如类对象等,直接return。点击了解更多关于nonpointer的信息。
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;
}
}
4、判断对象是否正在释放,如果是正在释放就没必要retain了
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
5、对引用技术加1,因为nonpointer对象的引用计数是存在isa指针里的有限为(指针长度64位,引用计数extra_rc总共占8位,RC_ONE (1ULL<<45),从地45位开始写 )(点击了解isa更多信息),所以有可能出现溢出情况,如果溢出就读取出当前引用计数的一半(RC_HALF)存储到SideTable(后面有分析)。
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;//更新isa中的extra_rc
newisa.has_sidetable_rc = true;
}
如果溢出,则RC_HALF存储到 SideTable:
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();
}
增加完引用计数之后就返回了。
release源码解析
release最终是通过方法objc_object::rootRelease来实现引用计数操作的。rootRelease主要做了以下几件事情:
1、读取isa指针oldisa:
oldisa = LoadExclusive(&isa.bits);
2、判断是否有自定义的release方法,如果有走自己的方法,没有就往下:
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_release()
// 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()) {
swiftRelease.load(memory_order_relaxed)((id)this);
return true;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
return true;
}
}
3、判断是否是nonpointer,如果不是就返回,不需要release:
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 false;
}
}
4、判断对象是否正在释放,如果是正在释放就退出循环跳到deallocate:
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
如果是正在释放则直接跳到deallocate:
if (slowpath(newisa.isDeallocating()))
goto deallocate;
5、引用计数extra_rc--,如果isa中的引用计数已经减为0了,则跳转到underflow:
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
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 (variant != RRVariant::Full) {
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.
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Try to remove some retain counts from the side table.
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) {
// Side table retain count decreased.
// Try to add them to the inline count.
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 && oldisa.nonpointer) {
// 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.
uintptr_t overflow;
newisa.bits =
addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
newisa.has_sidetable_rc = !emptySideTable;
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
if (stored) {
didTransitionToDeallocating = newisa.isDeallocating();
}
}
}
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.
}
}
在underflow流程中判断之前是否有用于存储引用计数的SideTable,如果有,从里面读取引用计数,然后减1,然后重新把引用计数同步更新到isa指针(方便下次读取),清除SideTable(清理内存)。
6、如果引用计数为0就会调用dealloc
deallocate:
// Really deallocate.
ASSERT(newisa.isDeallocating());
ASSERT(isa.isDeallocating());
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
dealloc源码解析
dealloc底层通过objc_object::rootDealloc()方法实现,其源码:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
1、这里我们看到它会判断当前对象是否有弱引用(weakly_referenced)、关联对象(has_assoc)、C++析构函数(has_cxx_dtor)、Sidetable(has_sidetable_rc),如果没有直接释放free(this);如果有,则调用object_dispose方法。object_dispose的流程如下:
object_dispose
->objc_destructInstance
->clearDeallocating
->clearDeallocating_slow
- > free(obj);
2、objc_destructInstances首先判断有没有C++析构函数和关联对象,有C++析构函数就调用,有关联对象remove:
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.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
clearDeallocating方法中会判断有没有弱引用或者SideTable中是否有引用计数has_sidetable_rc,如果有则调用clearDeallocating_slow处理(因为如引用表和部分引用计数表是存储于SideTable中,所以它们被放在一个方法里处理):
inline void
objc_object::clearDeallocating()
{
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就是读取isa和Sidetable(如果有)中的引用计数的和:
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();
}
SideTable表的作用
SideTable是个哈希表,适用于存储引用计数和弱引用表的,它的表结构如下:
这里有个问题:引用计数为什么要使用SideTable? 因为引用计数开始是存储在isa指针中的,但是由于isa指针位数有限(64位),而分配给引用技术8位,如果引用计数溢出,那么就需要一个引用计数表来存储多余的部分,SideTable是个哈希表,方便增删改查。
那么这里又有一个问题:既然isa指针位数有限,为什么不直接使用SideTable呢?主要是为了节省内存和提高读写效率。首先isa存储指针的初衷就是为了节省内存、提高读写效率的,而且对于大部分对象来说8位(2^8个)用于存储引用计数也已经足够,只有少部分对象引用计数可能会超过,而如果每个对象都使用SideTable表的话会影响效率,还浪费内存。
弱引用
弱引用不会增加对象的引用计数,但是它会有一个表来记录引用变量,用于当对象释放的时候把这些变量指针置为nil,防止野指针。有前面可知弱引用表是存储在SideTable中的(weak_table_t weak_table),下面是weak_table_t的数据结构:
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
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;
}
}
};
在对象释放的时候会把关联的弱引用表中的变量指针一个个删除,并把指针指向nil:
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
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);
}
自动释放池
关于自动释放池直接参考iOS自动释放池的底层原理。