美文网首页
引用计数原理

引用计数原理

作者: 六横六竖亚 | 来源:发表于2020-09-11 11:47 被阅读0次

ARC

代码编译阶段,在上下文中自动成对插入MRC下的retain和release方法,保证通过引用计数正确的管理内存(针对堆上)。

iOS中引用计数的存储方案

1、TaggedPointer下的小内存对象,直接返回指针值作为引用计数。NSString会根据长度(小于60字节)决定是否使用。但深拷贝后变为普通指针。

2、nonpointer_isa:OC2.0+64位,使用对象的isa指针的第一位标记是否使用了优化后的isa,后8位来存储引用计数(溢出后移出部分正常管理)。

3、Runtime使用一张散列hash表(SideTables)来管理,SideTable中的RefcountMap属性(还有weak表自旋锁两个属性)。注:objc_object::isTaggedPointer() 获取TAG_MASK标识位以判断是否使用了TaggedPoint。

SideTable

SideTables全局hash数组长度64,实际是StripedMap类型,里面储存了64个SideTable(为了避免资源争抢,所以不存一个表里),许多obj共用一个SideTable来存储引用计数和弱引用表相关信息。

SideTable结构体

自旋锁 spinlock_t slock  // 保证原子操作防止多线程读取问题

引用计数表 RefcountMap refcnts  // 散列表结构存储对象的持有者地址和引用计数,Zombie异常时也能定位对象地址信息。

弱引用表 weak_table_t weak_table  //保存了许多对象的,所有的weak引用,对象地址作为key,weak_entries作为值保存所有指向该对象的weak指针,dealloc时把所有weak指针设为nil,避免野指针。

注:static SideTable *tableForPointer(const void *p);  // 获取对象地址的sidetable

注:一个sidetable对应一个weaktable,一个weaktable对象中有无数个weakentry(通过对象地址作为key获取对应的),每个weakentry保存了这个对象的所有弱引用。

获取引用计数:retainCount

objc_object的rootRetainCount()方法

1、判断储存逻辑(TaggedPointer直接获取 / 优化后的isa,指针的后19位即extra_rc变量 / 散列表中获取)

2、sidetable_retainCount(),先SideTable::tableForPointer(this)获取SideTable对象,table.refcnts即引用计数的hash表

3、it != table->refcnts.end()(如果相等则引用计数返回1)根据键值对以对象为key获取引用计数的值并+1返回,所以实际计数应该为retainCount-1。

注:refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT(=2); // 返回时做了向右位移两位的操作,因为前两个bit,WEAKLY_REFERENCED标识是否有weak对象;DEALLOCATING是否正在析构。

修改引用计数:retain和release

总结:二者的实现机制类似,概括讲就是通过第一层 hash 算法,找到 指针变量 所对应的 sideTable。然后再通过一层 hash 算法,找到存储 引用计数 的 size_t,然后对其进行增减操作。retainCount 不是固定的 1,SIZE_TABLE_RC_ONE 是一个宏定义,实际上是一个值为 4 的偏移量。

retain方法

底层调用_objc_rootRetain、objc_object::rootRetain()、objc_object::sidetable_retain()、_slow四步方法,其中最重要的是增加引用计数的id objc_object::sidetable_retain()虚函数,详细实现如下:

SideTable& table = SideTables()[this];          // 传入对象地址获取对应的SideTable对象。

size_t& refcntStorage = table.refcnts[this];    // 获取 引用计数 的引用

!(refcntStorage & SIDE_TABLE_RC_PINNED) // 没有越界

refcntStorage += SIDE_TABLE_RC_ONE;      // 引用计数增加(实际增加了 1UL<<2 == 4)

注1:refcntStorage后两位被weak和析构状态占领,首位标识越界,所以不是增加1。

注2:refcnts为散列表,可能存了多个引用计数以处理引用计数越界情况,retainCount方法可以证明。

uintptr_t objc_object::sidetable_retainCount()  // 引用计数总返回1+计数表,所以总不为0

{ it != table.refcnts.end()  refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT; }

release方法

四步方法与retain类似,其中减少引用计数的函数uintptr_t objc_object::sidetable_release(bool performDealloc)实现总结如下:

1、内部判断是否dealloc:it == table.refcnts.end()(引用计数为表中最后一个,直接标记析构,table.refcnts[this] = SIDE_TABLE_DEALLOCATING)

2、it->second < SIDE_TABLE_DEALLOCATING(在-1前验证引用计数是否为0,如果是,标记正在析构并发送dealloc消息,否则才-1。do_dealloc=true; it->second |= SIDE_TABLE_DEALLOCATING;)

3、it->second -= SIDE_TABLE_RC_ONE(引用计数-1,实际偏移两位)

注:SIDE_TABLE_DEALLOCATING作为引用计数归0的判断,减少了标记变量内存的额外占用,也避免负数产生。注:为什么isa中的extra_rc、sidetable中的refcnts中,保存的值都是真正的引用计数-1?因为获取时是+1后返回的,保证了释放时-1不会出现负数。

相关文章

  • Objective-C 引用计数原理

    Objective-C 引用计数原理 Objective-C 引用计数原理

  • Objective-C 自动引用计数(ARC)的原理和内部实现

    预备知识:Objective-C 引用计数的原理和内部实现 自动引用计数(ARC)的原理建立在原有的手动引用计数的...

  • iOS内存管理

    1. 引用计数 1.1 引用计数原理 Objective-C 使用引用计数管理内存。新创建的对象引用计数至少为1,...

  • Effective Objective-C 2.0 总结(五)

    内存管理 第 29 条:理解引用计数 引用计数工作原理 Objective-C 语言使用引用计数来管理内存,每个对...

  • python垃圾回收机制

    python的垃圾回收机制: 主要是引用计数为主,分代回收为辅。 一、引用计数 引用计数的原理: // objec...

  • JVM之垃圾回收

    1、引用计数算法 引用计数算法的原理很简单:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值+1;当引用...

  • Effective Objective-C 2.0 第五章 内存

    第 29 条 理解引用计数 引用计数原理 引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后,其保留计...

  • iOS知识点

    1、引用计数是什么? 引用计数实际上是对内存地址的持有者的一个计数。 2、引用计数的工作原理是什么? ...

  • iOS内存管理原理

    关键字:引用计数 什么事引用计数,引用计数的原理是什么 引用计数是一个简单而有效的管理对象生命周期的方式。当我们创...

  • 第三章 垃圾收集器与内存分配策略

    对象存活判定算法 引用计数算法 原理是每个对象都有一个引用计数器,当被引用时,计数器加1,取消引用时计数器减1。计...

网友评论

      本文标题:引用计数原理

      本文链接:https://www.haomeiwen.com/subject/ucivektx.html