Swift底層原理-內(nèi)存管理

Swift底層原理-內(nèi)存管理

  • Swift語言延續(xù)了和Objective-C語言一樣的思路進(jìn)行內(nèi)存管理,都是采用引用計數(shù)的方式來管理實例的內(nèi)存空間琢蛤;

  • 在結(jié)構(gòu)體與類中我們了解到Swift對象本質(zhì)是一個HeapObject結(jié)構(gòu)體指針敷硅。HeapObject結(jié)構(gòu)中有兩個成員變量映胁,metadatarefCounts峭弟,metadata 是指向元數(shù)據(jù)對象的指針,里面存儲著類的信息弦撩,比如屬性信息,虛函數(shù)表等论皆。而 refCounts 通過名稱可以知道益楼,它是一個引用計數(shù)信息相關(guān)的東西。接下來我們研究一下 refCounts 点晴。

refCounts

  • 在源碼HeapObject.h文件中感凤,我們可以找到HeapObject結(jié)構(gòu)體中關(guān)于refCounts的定義
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;

  // 省略部分代碼
}
  • 我們看到 refCounts 的類型為 InlineRefCounts,在RefCount.h文件中找到 InlineRefCounts 的定義:
typedef RefCounts<InlineRefCountBits> InlineRefCounts;

RefCounts

  • 發(fā)現(xiàn)InlineRefCounts是一個模版類:RefCounts粒督,接收一個InlineRefCountBits類型的范型
template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;

  // Out-of-line slow paths.

  LLVM_ATTRIBUTE_NOINLINE
  void incrementSlow(RefCountBits oldbits, uint32_t inc) SWIFT_CC(PreserveMost);

  LLVM_ATTRIBUTE_NOINLINE
  void incrementNonAtomicSlow(RefCountBits oldbits, uint32_t inc);

  LLVM_ATTRIBUTE_NOINLINE
  bool tryIncrementSlow(RefCountBits oldbits);

  LLVM_ATTRIBUTE_NOINLINE
  bool tryIncrementNonAtomicSlow(RefCountBits oldbits);

  LLVM_ATTRIBUTE_NOINLINE
  void incrementUnownedSlow(uint32_t inc);

  public:
  enum Initialized_t { Initialized };
  enum Immortal_t { Immortal };
  // 省略部分方法
}
  • 根據(jù)RefCounts的定義我們發(fā)現(xiàn)陪竿,其實質(zhì)上是在操作我們傳遞的泛型參數(shù)InlineRefCountBits
  • 我們看一下InlineRefCountBits的定義
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
  • 它也是一個模板函數(shù),并且也有一個參數(shù) RefCountIsInline屠橄,而RefCountIsInline其實就是true族跛。我們重點看一下RefCountBitsT的結(jié)構(gòu)
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {

  friend class RefCountBitsT<RefCountIsInline>;
  friend class RefCountBitsT<RefCountNotInline>;
  
  static const RefCountInlinedness Inlinedness = refcountIsInline;

  typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;
  typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::SignedType
    SignedBitsType;
  typedef RefCountBitOffsets<sizeof(BitsType)>
    Offsets;

  BitsType bits;
    
  // 省略部分代碼
}
  • RefCountBitsT中,發(fā)現(xiàn)只有一個bits屬性锐墙,而該屬性是由RefCountBitsIntType屬性定義的礁哄;
  • 我們來看一下RefCountBitsInt的結(jié)構(gòu):
template <RefCountInlinedness refcountIsInline>
struct RefCountBitsInt<refcountIsInline, 8> {
  typedef uint64_t Type;
  typedef int64_t SignedType;
};
  • 可以看到,Type 的類型是一個 uint64_t 的位域信息溪北,在這個 uint64_t 的位域信息中存儲著運行生命周期的相關(guān)引用計數(shù)桐绒。

引用計數(shù)初始化流程

  • 我們創(chuàng)建一個新的實例對象時夺脾,他的引用計數(shù)是多少呢?從源碼中我們找到HeapObject的初始化方法:
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}
  • 調(diào)用了HeapObject初始化方法
constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
  • refCounts賦值了Initialized茉继,我們繼續(xù)分析發(fā)現(xiàn)Initialized是一個枚舉類型Initialized_t
enum Initialized_t { Initialized };
enum Immortal_t { Immortal };

// RefCounts must be trivially constructible to avoid ObjC++
// destruction overhead at runtime. Use RefCounts(Initialized)
// to produce an initialized instance.
RefCounts() = default;

// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
    : refCounts(RefCountBits(0, 1)) {}
  • 而根據(jù)注釋得知劳翰,一個新的實例被創(chuàng)建時,傳入的是RefCountBits(0馒疹,1)佳簸,并且我們可以看到 refCounts 函數(shù)的參數(shù)傳的不就是前面提到RefCountBitsT類型參數(shù),我們找到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))
{ }
  • 已知外部調(diào)用RefCountBitsT初始化方法颖变,strongExtraCount 傳 0生均,unownedCount 傳 1。
  • 然后我們?nèi)ゲ榭磶讉€偏移的定義
# define shiftAfterField(name) (name##Shift + name##BitCount)

template <>
struct RefCountBitOffsets<8> {  
  static const size_t PureSwiftDeallocShift = 0;
  static const size_t PureSwiftDeallocBitCount = 1;
  static const uint64_t PureSwiftDeallocMask = maskForField(PureSwiftDealloc);

  static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
  static const size_t UnownedRefCountBitCount = 31;
  static const uint64_t UnownedRefCountMask = maskForField(UnownedRefCount);

  static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
  static const size_t StrongExtraRefCountBitCount = 30;
  static const uint64_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
    
    // 結(jié)果分析
  StrongExtraRefCountShift = shiftAfterField(IsDeiniting)
                           = IsDeinitingShift + IsDeinitingBitCount
                           = shiftAfterField(UnownedRefCount) + 1
                           = UnownedRefCountShift + UnownedRefCountBitCount + 1
                           = shiftAfterField(PureSwiftDealloc) + 31 + 1
                           = PureSwiftDeallocShift + PureSwiftDeallocBitCount + 31 + 1
                           = 0 + 1 + 31 + 1 = 33
}
  • 通過上面計算得到: Offsets::StrongExtraRefCountShift = 33腥刹,Offsets::PureSwiftDeallocShift = 0马胧,Offsets::UnownedRefCountShift = 1
  • 知道了這三個值的之后,我們開始計算RefCountBitsT的初始化方法調(diào)用 bits 的值:
0 << 33 | 1 << 0 | 1 << 1;
0 | 1 | 2 = 3;
  • 最終bits存儲信息如下:

[圖片上傳失敗...(image-b8859d-1670758320398)]

  • 0位:標(biāo)識是否是永久的
  • 1-31位:存儲無主引用
  • 32位:標(biāo)識當(dāng)前類是否正在析構(gòu)
  • 33-62位:標(biāo)識強引用
  • 63位:是否使用SlowRC

強引用

  • 默認(rèn)情況下衔峰,引用都是強引用佩脊。通過前面對refCounts的結(jié)構(gòu)分析,得知它是存儲引用計數(shù)信息的東西垫卤,在創(chuàng)建一個對象之后它的初始值為 0x0000000000000003威彰。

  • 如果我對這個實例對象進(jìn)行多個引用,引用計數(shù)會增加穴肘,那這個強引用是如何添加的歇盼?

  • 底層會通過調(diào)用_swift_retain_方法

static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.increment(1);
  return object;
}
  • 在進(jìn)行強引用的時候,本質(zhì)上是調(diào)用 refCountsincrement 方法评抚,也就是引用計數(shù) +1豹缀。我們來看一下 increment 的實現(xiàn):
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;
    }

    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));
}
  • increment 中調(diào)用了 incrementStrongExtraRefCount,我們再去看看incrementStrongExtraRefCount實現(xiàn)
LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
    bool incrementStrongExtraRefCount(uint32_t inc) {
    // This deliberately overflows into the UseSlowRC field.
    bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
    return (SignedBitsType(bits) >= 0);
}
  • 此時inc1慨代,StrongExtraRefCountShift根據(jù)之前的計算為33
  • 1 << 33為結(jié)果為8589934592,其對應(yīng)的十六進(jìn)制為0x200000000
  • 到這里就實現(xiàn)了引用計數(shù)+1的操作

弱引用

  • 在實際開發(fā)的過程中邢笙,我們大多使用的都是強引用,在某些場景下使用強引用侍匙,用不好的話會造成循環(huán)引用氮惯。
  • Swift中我們通過關(guān)鍵字weak來表明一個弱引用;weak關(guān)鍵字的作用是在使用這個實例的時候并不保有此實例的引用丈积。使用weak關(guān)鍵字修飾的引用類型數(shù)據(jù)在傳遞時不會使引用計數(shù)加1筐骇,不會對其引用的實例保持強引用,因此不會阻止ARC釋放被引用的實例江滨。
  • 由于弱引用不會保持對實例的引用铛纬,所以當(dāng)實例被釋放的時候,弱引用仍舊引用著這個實例也是有可能唬滑。因此告唆,ARC會在被引用的實例釋放時棺弊,自動地將弱引用設(shè)置為nil。由于弱引用需要允許設(shè)置為nil擒悬,因此它一定是可選類型模她;

swift_weakInit

  • weak 修飾之后,變量變成了一個可選項懂牧,并且侈净,還會調(diào)用一個 swift_weakInit 函數(shù)
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return ref;
}
  • 發(fā)現(xiàn)用 weak 修飾之后,在內(nèi)部會生成WeakReference類型的變量僧凤,并在 swift_weakInit 中調(diào)用 nativeInit 函數(shù)畜侦。
void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
  • nativeInit方法中調(diào)用了formWeakReference()方法,也就意味著形成了弱引用(形成一個散列表):
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}
  • 它本質(zhì)就是創(chuàng)建了一個散列表
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
  // 去除原有的refCount躯保,也是是64位信息
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
  
  // Preflight failures before allocating a new side table.
  // 判斷原來的 refCounts 是否有當(dāng)前的引用計數(shù)
  if (oldbits.hasSideTable()) {
    // 如果有直接返回
    return oldbits.getSideTable();
  } 
  else if (failIfDeiniting && oldbits.getIsDeiniting()) {
    // 如果沒有并且正在析構(gòu)直接返回 nil
    return nullptr;
  }

  // Preflight passed. Allocate a side table.
  
  // 創(chuàng)建一個散列表
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  
  auto newbits = InlineRefCountBits(side);
  
  // 對原來的散列表以及正在析構(gòu)的一些處理
  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;
}
  • 散列表的創(chuàng)建可以分為4步:
    1. 取出原來的 refCounts引用計數(shù)的信息旋膳。
    2. 判斷原來的 refCounts 是否有散列表,如果有直接返回途事,如果沒有并且正在析構(gòu)直接返回nil验懊。
    3. 創(chuàng)建一個散列表。
    4. 對原來的散列表以及正在析構(gòu)的一些處理尸变。

HeapObjectSideTableEntry

  • 接下來我們來看看這個散列表HeapObjectSideTableEntry
Storage layout:

HeapObject {
  isa
  InlineRefCounts {
    atomic<InlineRefCountBits> {
      strong RC + unowned RC + flags
      OR
      HeapObjectSideTableEntry*
    }
  }
}

HeapObjectSideTableEntry {
  SideTableRefCounts {
    object pointer
    atomic<SideTableRefCountBits> {
      strong RC + unowned RC + weak RC + flags
    }
  }   
}
  • 可以分析出在Swift中本質(zhì)上存在兩種引用計數(shù):
    • 如果是強引用义图,那么是strong RC + unowned RC + flags
    • 如果是弱引用振惰,那么是 HeapObjectSideTableEntry歌溉;
  • 我們看一下HeapObjectSideTableEntry結(jié)構(gòu)
class HeapObjectSideTableEntry {
  // FIXME: does object need to be atomic?
  std::atomic<HeapObject*> object;
  SideTableRefCounts refCounts;

  public:
  HeapObjectSideTableEntry(HeapObject *newObject)
    : object(newObject), refCounts()
  { }
  
  // 省略部分代碼
}
  • 可以看到,HeapObjectSideTableEntry中存著對象的指針骑晶,并且還有一個 refCounts,而 refCounts 的類型為SideTableRefCounts
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
  • SideTableRefCountBits就是繼承自我們前面學(xué)過的RefCountBitsT的模版類
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
  uint32_t weakBits;

  public:
  LLVM_ATTRIBUTE_ALWAYS_INLINE
  SideTableRefCountBits() = default;
}
  • 它多了一個weakBits成員變量草慧。
  • 所以HeapObjectSideTableEntry里邊存儲的是64位原有的strong RC + unowned RC + flags桶蛔,再加上32位weak RC
  • 當(dāng)我們用 weak 修飾之后漫谷,這個散列表就會存儲對象的指針和引用計數(shù)信息相關(guān)的東西仔雷。

無主引用

  • Swift中可以通過 unowned 定義無主引用,unowned 不會產(chǎn)生強引用舔示,實例銷毀后仍然存儲著實例的內(nèi)存地址(類似于OC中的 unsafe_unretained)碟婆。需要注意的是試圖在實例銷毀后訪問無主引用,會產(chǎn)生運行時錯誤(野指針)惕稻。

  • weak竖共、unowned 都能解決循環(huán)引用的問題,unowned 要比 weak 少一些性能消耗俺祠,那我們?nèi)绾蝸磉x擇 weakunowned 呢?

    • 如果強引用的雙方生命周期沒有任何關(guān)系公给,使用weak
    • 如果其中一個對象銷毀借帘,另一個對象也跟著銷毀,則使用unowned淌铐;
  • weak相對于unowned更兼容肺然,更安全,而unowned性能更高腿准;這是因為weak需要操作散列表际起,而unowned只需要操作64位位域信息;在使用unowned的時候吐葱,要確保其修飾的屬性一定有值加叁。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市唇撬,隨后出現(xiàn)的幾起案子它匕,更是在濱河造成了極大的恐慌,老刑警劉巖窖认,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豫柬,死亡現(xiàn)場離奇詭異,居然都是意外死亡扑浸,警方通過查閱死者的電腦和手機烧给,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喝噪,“玉大人础嫡,你說我怎么就攤上這事≡途澹” “怎么了榴鼎?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晚唇。 經(jīng)常有香客問我巫财,道長,這世上最難降的妖魔是什么哩陕? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任平项,我火速辦了婚禮,結(jié)果婚禮上悍及,老公的妹妹穿的比我還像新娘闽瓢。我一直安慰自己,他們只是感情好心赶,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布扣讼。 她就那樣靜靜地躺著,像睡著了一般园担。 火紅的嫁衣襯著肌膚如雪届谈。 梳的紋絲不亂的頭發(fā)上枯夜,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音艰山,去河邊找鬼湖雹。 笑死,一個胖子當(dāng)著我的面吹牛曙搬,可吹牛的內(nèi)容都是我干的摔吏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼纵装,長吁一口氣:“原來是場噩夢啊……” “哼征讲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起橡娄,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诗箍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挽唉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滤祖,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年瓶籽,在試婚紗的時候發(fā)現(xiàn)自己被綠了匠童。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墩虹,到底是詐尸還是另有隱情,我是刑警寧澤皮服,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏勒奇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一巧骚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧格二,春花似錦劈彪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至长窄,卻和暖如春滔吠,著一層夾襖步出監(jiān)牢的瞬間纲菌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工疮绷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翰舌,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓冬骚,卻偏偏與公主長得像椅贱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子只冻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容