本文首发于我的博客html
维基百科中这么定义引用计数c++
引用计数是计算机编程语言中的一种内存管理技术,是指将资源(能够是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术能够实现自动资源管理的目的。同时引用计数还能够指使用引用计数技术回收未使用资源的垃圾回收算法。git
当建立一个对象的实例并在堆上申请内存时,对象的引用计数就为1,在其余对象中须要持有这个对象时,就须要把该对象的引用计数加1,须要释放一个对象时,就将该对象的引用计数减1,直至对象的引用计数为0,对象的内存会被马上释放。github
一个新建立的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间算法
调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1编程
内存管理的经验总结bash
能够经过如下私有函数来查看自动释放池的状况架构
extern void _objc_autoreleasePoolPrint(void)
;在详解iOS中的Runtime一文中,对isa进行了详解。app
这里进行简单概述编程语言
从arm64架构开始,苹果对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。以下
define ISA_BITFIELD \
uintptr_t nonpointer : 1; //指针是否优化过 \
uintptr_t has_assoc : 1; //是否有设置过关联对象,若是没有,释放时会更快 \
uintptr_t has_cxx_dtor : 1; //是否有C++的析构函数(.cxx_destruct),若是没有,释放时会更快 \
uintptr_t shiftcls : 33; //存储着Class、Meta-Class对象的内存地址信息 \
uintptr_t magic : 6; //用于在调试时分辨对象是否未完成初始化 \
uintptr_t weakly_referenced : 1; //是否有被弱引用指向过,若是没有,释放时会更快 \
uintptr_t deallocating : 1; //对象是否正在释放 \
uintptr_t has_sidetable_rc : 1; //引用计数器是否过大没法存储在isa中 \
uintptr_t extra_rc : 19 //里面存储的值是引用计数器减1
复制代码
nonpointer
has_assoc
has_cxx_dtor
shiftcls
magic
weakly_referenced
deallocating
extra_rc
has_sidetable_rc
再开始以前,先看这个代码
NSNumber *num = @(20);
咱们只有一个须要存储20这个数据,按照正常的技术方案,在64位CPU下,应该先去建立NSNumber对象,其值是20,而后再有个指向该地址的指针num
。这样作存在什么问题呢?
内存浪费
性能浪费
为了解决这个问题,苹果提出了Tagged Pointer的概念。对于 64 位程序,引入 Tagged Pointer 后,相关逻辑能减小一半的内存占用,以及 3 倍的访问速度提高,100 倍的建立、销毁速度提高。
从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
在没有使用Tagged Pointer以前, NSNumber等对象须要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
使用Tagged Pointer以后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
objc_msgSend
能识别Tagged Pointer,好比NSNumber的intValue方法,直接从指针提取数据,节省了之前的调用开销
如何判断一个指针是否为Tagged Pointer?
关于Tagged Pointer,想深刻了解的,能够参照深刻理解 Tagged Pointer,就不在这赘述了。须要注意的是,以前的版本,变量的值直接存储在指针中,很容易的能够读取出来,例如0xb000000000000012
然而如今的版本中,苹果对这个指针作了一些编码处理,不能直接看出来是Tagged Pointer,例如0x30a972fb5e339e15
然而它依然是Tagged Pointer,由于能够根据源码可知,是根据把它转为二进制以后最后一位是否为1来肯定是否为Tagged Pointer。
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
复制代码
在64bit中,引用计数能够直接存储在优化过的isa指针中,也可能存储在SideTable类中,那SideTable
中有什么呢?
SideTable
的结构以下
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;//refcnts是一个存放着对象引用计数的散列表
weak_table_t weak_table;
...还有不少代码
};
复制代码
其中 RefcountMap refcnts
中存放着对象引用计数的散列表
// 引用计数
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
复制代码
rootRetainCount
inline uintptr_t
objc_object::rootRetainCount()
{
//TaggedPointer不是一个普通的对象,不须要作引用计数的一些操做
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) { //优化过的isa
uintptr_t rc = 1 + bits.extra_rc; // 这里进行了+1操做
if (bits.has_sidetable_rc) {
//能来到这里,说明引用计数不是存储在isa中,而是存储在sidetable中
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
复制代码
sidetable_getExtraRC_nolock
size_t
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this]; // this 就是key 根据这个key取出value
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT; // 取出的值 通过位运算以后返回
}
复制代码
sidetable_retainCount()
方法的逻辑就是先从 SideTable
的静态方法获取当前实例对应的 SideTable
对象,其 refcnts
属性就是以前说的存储引用计数的散列表,而后在引用计数表中用迭代器查找当前实例对应的键值对,获取引用计数值,并在此基础上 +1 并将结果返回。这也就是为何以前中说引用计数表存储的值为实际引用计数减一。
须要注意的是为何这里把键值对的值作了向右移位操做(it->second >> SIDE_TABLE_RC_SHIFT)
在MRC 环境下可使用 retain 和 release 方法对引用计数进行加一减一操做,它们分别调用了_objc_rootRetain(id obj)
和 _objc_rootRelease(id obj)
函数,不事后二者在 ARC 环境下也可以使用。最后这两个函数又会调用 objc_object 的下面两个方法:
inline id
objc_object::rootRetain()
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
inline bool
objc_object::rootRelease()
{
assert(!UseGC);
if (isTaggedPointer()) return false;
return sidetable_release(true);
}
复制代码
就是先看释放支持isTaggedPointer,而后再操做 SideTable 中的 refcnts 属性,这与获取引用计数策略相似。sidetable_retain() 将 引用计数加一后返回对象,sidetable_release() 返回是否要执行 dealloc 方法:
引用计数的增长
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 (!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; 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; newisa.extra_rc = RC_HALF; 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. sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; } 复制代码
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
复制代码
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;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);//引用计数减小
}
// 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(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_release
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it. do_dealloc = true; it->second |= SIDE_TABLE_DEALLOCATING; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { it->second -= SIDE_TABLE_RC_ONE; } table.unlock(); if (do_dealloc && performDealloc) {// 来到这里,说明引用计数为0,调用dealloc释放 ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return do_dealloc; } 复制代码
更多资料,欢迎关注我的公众号,不定时分享各类技术文章。