<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          聊一聊 C# 弱引用 底層是怎么玩的

          共 9849字,需瀏覽 20分鐘

           ·

          2024-07-14 22:01

          一:背景

          1. 講故事

          最近在分析dump時,發(fā)現(xiàn)有程序的卡死和WeakReference有關(guān),在以前只知道怎么用,但不清楚底層邏輯走向是什么樣的,借著這個dump的契機來簡單研究下。

          二:弱引用的玩法

          1. 一些基礎(chǔ)概念

          用過WeakReference的朋友都知道這里面又可以分為弱短弱長兩個概念,對應(yīng)著構(gòu)造函數(shù)中的trackResurrection參數(shù),同時它也是對底層GCHandle.Alloc 方法的封裝,參考源碼如下:


          public WeakReference(object? target, bool trackResurrection)
          {
              Create(target, trackResurrection);
          }

          private void Create(object target, bool trackResurrection)
          {
              nint num = GCHandle.InternalAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak);
              _taggedHandle = (trackResurrection ? (num | 1) : num);
              ComAwareWeakReference.ComInfo comInfo = ComAwareWeakReference.ComInfo.FromObject(target);
              if (comInfo != null)
              {
                  ComAwareWeakReference.SetComInfoInConstructor(ref _taggedHandle, comInfo);
              }
          }

          public enum GCHandleType
          {
              //
              // Summary:
              //     This handle type is used to track an object, but allow it to be collected. When
              //     an object is collected, the contents of the System.Runtime.InteropServices.GCHandle
              //     are zeroed. Weak references are zeroed before the finalizer runs, so even if
              //     the finalizer resurrects the object, the Weak reference is still zeroed.
              Weak = 0,
              //
              // Summary:
              //     This handle type is similar to System.Runtime.InteropServices.GCHandleType.Weak,
              //     but the handle is not zeroed if the object is resurrected during finalization.
              WeakTrackResurrection = 1
          }

          從上面的 GCHandleType 的注釋來看。

          • Weak 會在終結(jié)器執(zhí)行之前判斷持有的對象是否為垃圾對象,如果是的話直接切斷引用。
          • WeakTrackResurrection 會在終結(jié)器執(zhí)行之后判斷對象是否為垃圾對象,如果是的話直接切斷引用。

          可能這么說有點抽象,畫張圖如下:

          2. 一個簡單的測試例子

          為了方便講述兩者的區(qū)別,使用 對象復(fù)活 來做測試。

          1. Weak 的情況

          因為在 ScanForFinalization 方法之前做的判斷,所以與垃圾對象的聯(lián)系會被馬上切斷,參考代碼如下:


              class Program
              {
                  static void Main()
                  {
                      WeakReferenceCase();

                      GC.Collect();
                      GC.WaitForPendingFinalizers();

                      Console.WriteLine(weakHandle.Target ?? "Person 引用被切斷");

                      Console.ReadLine();
                  }

                  public static GCHandle weakHandle;

                  static void WeakReferenceCase()
                  {
                      var person = new Person() { ressurect = false };
                      weakHandle = GCHandle.Alloc(person, GCHandleType.Weak);
                  }
              }

              public class Person
              {
                  public bool ressurect = false;

                  ~Person()
                  {
                      if (ressurect)
                      {
                          Console.WriteLine("Person 被永生了,不可能被消滅的。。。");
                          GC.ReRegisterForFinalize(this);
                      }
                      else
                      {
                          Console.WriteLine("Person 析構(gòu)已執(zhí)行...");
                      }
                  }
              }

          1. WeakTrackResurrection 的情況

          因為是在 ScanForFinalization 之后做的判斷,這時候可能會存在 對象復(fù)活 的情況,所以垃圾又變成不垃圾了,如果是這種情況就不能切斷,參考代碼如下:


          static void WeakReferenceCase()
          {
              var person = new Person() { ressurect = true };
              weakHandle = GCHandle.Alloc(person, GCHandleType.WeakTrackResurrection);
          }

          3. coreclr源碼分析

          在 coreclr 里有一個 struct 枚舉強對應(yīng) GCHandleType 結(jié)構(gòu)體,而且名字看的更加清楚,代碼如下:


          typedef enum
          {
           HNDTYPE_WEAK_SHORT = 0,
           HNDTYPE_WEAK_LONG = 1,
          }
          HandleType;

          接下來看下剛才截圖源碼上的驗證。


          void gc_heap::mark_phase(int condemned_gen_number, BOOL mark_only_p)
          {
           // null out the target of short weakref that were not promoted.
           GCScan::GcShortWeakPtrScan(condemned_gen_number, max_generation, &sc);

           dprintf(3, ("Finalize marking"));
           finalize_queue->ScanForFinalization(GCHeap::Promote, condemned_gen_number, mark_only_p, __this);

           // null out the target of long weakref that were not promoted.
           GCScan::GcWeakPtrScan(condemned_gen_number, max_generation, &sc);
          }

          BOOL CFinalize::ScanForFinalization(promote_func* pfn, int gen, BOOL mark_only_p, gc_heap* hp)
          {
              for (unsigned int Seg = startSeg; Seg <= gen_segment(0); Seg++)
              {
                  Object** endIndex = SegQueue(Seg);
                  for (Object** i = SegQueueLimit(Seg) - 1; i >= endIndex; i--)
                  {
                      CObjectHeader* obj = (CObjectHeader*)*i;

                      if (!g_theGCHeap->IsPromoted(obj))
                      {
                          if (method_table(obj)->HasCriticalFinalizer())
                          {
                              MoveItem(i, Seg, CriticalFinalizerListSeg);
                          }
                          else
                          {
                              MoveItem(i, Seg, FinalizerListSeg);
                          }
                      }
                  }
              }

              if(finalizedFound) GCToEEInterface::EnableFinalization(true);

              return finalizedFound;
          }

          源碼中有幾個注意點:

          1. 如何判斷一個對象為垃圾

          gc 在標記時,將有根的對象mt的第一位設(shè)為 1 來表示當前已經(jīng)標記過,即有用對象,未被標記的即為垃圾對象。

          1. 終結(jié)器線程真的被啟動了嗎

          從簡化的源碼看,一旦有垃圾對象被送入到 終結(jié)器隊列的 預(yù)備區(qū) 時,就會通過 GCToEEInterface::EnableFinalization(true) 啟動終結(jié)器線程,所以在測試代碼中加了 GC.WaitForPendingFinalizers(); 就是為了等待終結(jié)器線程執(zhí)行完畢然后才判斷 Target,這樣結(jié)果就會更加準確。

          4. 切斷邏輯在哪里

          有些朋友會好奇那個 weakHandle.Target=null 的邏輯到底在 coreclr 的何處,這個比較簡單,可以用 windbg 下 ba 斷點即可,我們還是拿弱引用來舉例,截圖如下:

          三:總結(jié)

          WeakReference 的內(nèi)部玩法有很多,更深入的理解還需要對 g_HandleTableMap 進行深度挖掘,后面有機會再聊吧,有時候dump分析還是挺苦逼的,需要對相關(guān)領(lǐng)域底層知識有一個足夠了解,否則談何修復(fù)呢?







          回復(fù) 【關(guān)閉】學(xué)永久關(guān)閉App開屏廣告
          回復(fù) 【刪除】學(xué)自動檢測那個微信好友刪除、拉黑
          回復(fù) 【手冊】獲取3萬字.NET、C#工程師面試手冊
          回復(fù) 【幫助】獲取100+個常用的C#幫助類庫
          回復(fù) 【加群】加入DotNet學(xué)習(xí)交流群


          瀏覽 83
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲国产无码在线观看 | A片视频免费看 | 青青草免费视频在线 | 久久影院国产 | 久久 大香蕉 |