Swift底层探索(六)Swift内存管理的源码分析

1,373 阅读6分钟

我正在参加「掘金·启航计划」

主要学习强引用、弱引用、循环引用的解决,重点在于强引用和弱引用的底层结构

主要内容:

  1. 强引用
  2. 弱引用

1. 强引用

通过一个对象的创建,在源码中查看引用计数

1.1 创建

代码:

class  WYTeacher {
    var age: Int = 18
    var name: String = " WY"
}
var t =  WYTeacher()
var t1 = t
var t2 = t

查看:

说明:

  • 直接将对象赋值给一个变量,就是强引用
  • 可以查看此时的refCounts的值

1.2 refCounts的认识

通过在底层源码中查找HeapObject来查看对象引用的计数。 引用计数refCounts属性其本质是RefCountsInt类型,包含unit32_t Type和int32_t SignedType

1.2.1. HeapObject

代码:

struct HeapObject {
  HeapMetadata const *metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
  ...
}
👇
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

说明:

  • 对象在底层是HeapObject
  • 所以在源码中查看HeapObject,发现引用是SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
  • 通过SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS的定义可以看到是refCounts的类型是InlineRefCounts

1.2.2. InlineRefCounts

代码:

typedef RefCounts<InlineRefCountBits> InlineRefCounts;
👇
template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ...
}

说明:

  • 查看InlineRefCounts,发现它是RefCounts的别名
  • 而RefCounts的成员是refCounts,它是由RefCountBits决定的
  • 因此是InlineRefCounts通过InlineRefCountBits来决定里面的值

1.2.3. InlineRefCountBits

代码:

typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;

说明:

  • 继续查找InlineRefCountBits;,发现它是RefCountBitsT的别名

1.2.4. RefCountBitsT

代码:

template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
    ...
      typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;
    ...
    BitsType bits;
    ...
}
👇
template <>
struct RefCountBitsInt<RefCountNotInline, 4> {
  //类型
  typedef uint64_t Type;
  typedef int64_t SignedType;
};

说明:

  • 可以看到有一个bits属性,这个属性就是Bits
  • bits其实质是将RefCountBitsInt中的type属性取了一个别名
  • 所以bits的真正类型是uint64_t即64位整型的数组,包含Type和SignedType两个成员

可以看出refCounts中其实包含有Bits成员,而这个成员是64位整型的数组。

1.3 refCounts的赋值探索

通过探索swift_allocObject,来查看引用属性的的赋值过程

1.3.1. swift_allocObject

代码:

static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
    ...
    new (object) HeapObject(metadata);
    ...
}
👇
<!--构造函数-->
 constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }

说明:

  • 通过swift_allocObject方法来构造对象,因此在这里会进行对象中引用属性的赋值
  • 可以看到是通过refCounts来给引用计数赋值的,传入的参数是Initialized

1.3.2. Initialized

代码:

enum Initialized_t { Initialized };
  
  //对应的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(01)) {}

说明:

  • 进入Initialized定义,是一个枚举
  • 其对应的refCounts方法中,看出真正干事的是RefCountBits

1.3.3. RefCountBits

代码:

template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ...
}

说明:

  • 进入RefCountBits定义,也是一个模板定义

1.3.4. RefCountBitsT

代码:

LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
       (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
       (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
{ }

说明:

  • 这里就是真正的初始化地方
  • 实际上是做了一个位域操作

1.4 RefCountsBit结构

经过上文的查找,可以得到RefCountsBit结构

16620449989227.jpg

说明:

  • isImmortal(0)
  • UnownedRefCount(1-31):unowned的引用计数
  • isDeinitingMask(32):是否进行释放操作
  • StrongExtraRefCount(33-62):强引用计数
  • UseSlowRC(63)

1.5 分析SIL代码

1.5.1. 创建对象,第一次变量赋值

代码:

class  WYTeacher {
    var age: Int = 18
    var name: String = " WY"
}

var t = WYTeacher()
var t1 = t

SIL文件:

16620926238132.jpg

说明:

  1. 先创建一个全局的实例变量
  2. 之后创建对象
  3. 将对象赋值给全局变量

1.5.2. 第二次变量赋值

SIL文件:

16620929057947.jpg

说明:

  1. 先创建t1的变量
  2. 之后通过%3拿到对象
  3. 通过copy_addr将拿到的对象赋值给t1
    1. %new = load $*LGTeacher(拿到这个对象)
    2. strong_retain %new(给这个对象引用计数加一)
    3. store %new to %9(将这个对象赋给t1)
  4. copy_addr最主要的就是对这个对象做了一下strong_ratain。之后再进行赋值操作

1.5.3. swift_retain的认识

strong_retain对应的就是 swift_retain,其内部是一个宏定义,内部是_swift_retain_,其实现是对object的引用计数作+1操作。

SIL文件:

//内部是一个宏定义
HeapObject *swift::swift_retain(HeapObject *object) {
  CALL_IMPL(swift_retain, (object));
}
👇
//本质调用的就是 _swift_retain_
static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.increment(1);
  return object;
}
👇
void increment(uint32_t inc = 1) {
    auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
    
    // constant propagation will remove this in swift_retain, it should only
    // be present in swift_retain_n
    if (inc != 1 && oldbits.isImmortal(true)) {
      return;
    }
    //64位bits
    RefCountBits newbits;
    do {
      newbits = oldbits;
      bool fast = newbits.incrementStrongExtraRefCount(inc);
      if (SWIFT_UNLIKELY(!fast)) {
        if (oldbits.isImmortal(false))
          return;
        return incrementSlow(oldbits, inc);
      }
    } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                              std::memory_order_relaxed));
  }

说明:

  • 通过refCounts.increment(1);进行引用计数+1
  • 在increment方法中是通过incrementStrongExtraRefCount来实现计数的

1.5.4. incrementStrongExtraRefCount

SIL文件:

LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
// 对inc做强制类型转换为 BitsType
// 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等价于 1<<33位,16进制为 0x200000000
//这里的 bits += 0x200000000,将对应的33-63转换为10进制,为
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}

说明:

  • 通过位域进行偏移
  • 我们知道refCounts中的62-33位是strongCount,这里的位域目的就是将引用计数加在这里
  • 每次的增加为1左移33位
    • 也就是每次增加0x200000000
    • 也就是BitsType(inc) << Offsets::StrongExtraRefCountShift

1.6 引用计数的计算

因为refCounts中的62-33位是strongCount,所以每次引用计数+1,都要加上0x200000000。BitsType(inc) << Offsets::StrongExtraRefCountShift就代表1<<33位。

计算:

  1. 只有t时的refCounts是 0x0000000200000003
  2. t + t1时的refCounts是 0x0000000400000003 = 0x0000000200000003 + 0x200000000
  3. t + t1 + t2 时的refCounts是 0x0000000600000003 = 0x0000000400000003 + 0x200000000

运行查看:

16621002858915.jpg

说明:

  • 可以看到swift中创建实例对象时默认为1
  • 可以通过CFGetRetainCount查到一个对象的引用计数

1.7 总结

  • swift中创建实例对象时默认为1
  • 强引用计数存储在refCounts中的62-33位,因此每次引用计数+1,都要加上0x200000000

2. 弱引用

主要认识弱引用的使用、以及散列表的存储结构。散列表不仅存储了弱引用,还有强引用和无主引用,如果有弱引用,在refCounts中直接使用散列表的地址

2.1 弱引用的使用

代码:

class WYTeacher {
    var age: Int = 18
    var name: String = "WY"
    var stu: WYStudent?
}

class WYStudent {
    var age = 20
    var teacher: WYTeacher?
}

func test(){
    var t = WYTeacher()
    weak var t1 = t
}

说明:

  • 给变量t赋值给弱引用t1
  • 弱引用声明的变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为nil的

2.2 debug查看底层方法

查看引用计数变化:

16621005767777.jpg

在t1处加断点,查看汇编:

16621006401196.jpg

说明:

  • 可以看到在底层是使用swift_weakInit来设置弱引用的
  • refCounts处存储的是一个地址

2.3 底层源码探索

2.3.1. swift_weakInit

代码:

WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return ref;
}

说明:

  • 查看swift_weakInit,可以看到系统本身提供了一个WeakReference类,用来进行引用计数的创建
  • 这个ref对象调用nativeInit,传入对象,就可以创建弱引用了

2.3.2. nativeInit

代码:

void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}

说明:

  • 在方法中,通过对象的refCounts属性的formWeakReference()方法来具体实现
  • 这个在对象底层结构中可以知道有这个方法

2.3.3. formWeakReference

代码:

template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  //创建 sideTable
  auto side = allocateSideTable(true);
  if (side)
  // 如果创建成功,则增加弱引用
    return side->incrementWeak();
  else
    return nullptr;
}

说明:

  1. 创建散列表
  2. 通过incrementWeak()给散列表增加弱引用

2.3.4. allocateSideTable

代码:

template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
  // 1、先拿到原本的引用计数
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
  
  // Preflight failures before allocating a new side table.
  if (oldbits.hasSideTable()) {
    // Already have a side table. Return it.
    return oldbits.getSideTable();
  } 
  else if (failIfDeiniting && oldbits.getIsDeiniting()) {
    // Already past the start of deinit. Do nothing.
    return nullptr;
  }

  // Preflight passed. Allocate a side table.
  
  // FIXME: custom side table allocator
  //2、创建sideTable
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  // 3、将创建的地址给到InlineRefCountBits
  auto newbits = InlineRefCountBits(side);
  
  do {
    if (oldbits.hasSideTable()) {
      // Already have a side table. Return it and delete ours.
      // Read before delete to streamline barriers.
      auto result = oldbits.getSideTable();
      delete side;
      return result;
    }
    else if (failIfDeiniting && oldbits.getIsDeiniting()) {
      // Already past the start of deinit. Do nothing.
      return nullptr;
    }
    
    side->initRefCounts(oldbits);
    
  } while (! refCounts.compare_exchange_weak(oldbits, newbits,
                                             std::memory_order_release,
                                             std::memory_order_relaxed));
  return side;
}

说明:

  • 通过allocateSideTable构建散列表
    1. 先拿到原本的引用计数
    • 散列表也需要存储强引用的引用计数
    • 如果这个bits本来就有散列表了,就直接使用原有的散列表
    1. 创建散列表
    • 这个散列表只是针对当前对象的
    1. 通过InlineRefCountBits将散列表的地址赋给值newBits
    • 因此如果有弱引用,那么引用计数中存储的就是散列表地址

2.3.5. 查看弱引用的RefCountBitsT的创建过程

代码:

16621343773479.jpg

说明:

  • 传入的参数是HeapObjectSideTableEntry的方法,就是用来构建散列表的Bits
  • 将散列表地址通过偏移操作存储到RefCountBits内存中
  • 在这里可以看到,除去63、62位,剩下的地方用来存储了散列表地址

2.3.6. HeapObjectSideTableEntry

代码:

16621344827499.jpg

说明:

  • 查看散列表的结构
  • 包括object和refCounts
  • 其实可以看做分别将对象和引用计数作为键和值
  • 这里的引用计数表就是散列表

2.3.7. SideTableRefCountBits

代码:

16621345564855.jpg

说明:

  • 继承自RefCountBitsT
  • 只有一个属性weakBits,是弱引用属性
  • 在RefCountBitsT中还有一个属性,64位用来记录原有的强引用计数。

2.4 验证

代码:

16621357838486.jpg

计算过程:

Kapture 2022-09-03 at 00.20.22.gif

说明:

  1. 散列表地址 = refCounts 清除63、62位 + 左移三位
  2. 上面我们拿到了弱引用后的地址0xc000000020261de6
  3. 将62、63清零,变成0x20261DE6
  4. 然后左移3位变成0x10130EF30,这个就是散列表地址
  5. 查看散列表内容
    1. 包含两个内容,一个是对象,所以会打印对象的地址
    2. 一个是引用表,引用表包含强引用计数和弱引用计数

2.5 总结

  • 当给一个对象增加弱引用后,会创建一个散列表用来存储弱引用和强引用
  • 并且将散列表的地址存储到bits中
  • 因此对于HeapObject有两种refCounts方式
    • 没有弱引用
      • 没有弱引用,只有强引用和无主引用
      • strongCount + unonwnedCount
    • 有弱引用
      • 如果有弱引用,那么存储的是一个散列表地址
      • 而散列表中存储的是object和散列表信息
      • 散列表信息又包含了weakBits
      • 同时又因为继承RefCountBitsT,所以依然会包含强引用和无主引用。