最近在分析dump時,發(fā)現(xiàn)有程序的卡死和WeakReference有關(guān),在以前只知道怎么用,但不清楚底層邏輯走向是什么樣的,借著這個dump的契機來簡單研究下。
用過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 的注釋來看。
可能這么說有點抽象,畫張圖如下:
圖片
為了方便講述兩者的區(qū)別,使用 對象復(fù)活 來做測試。
因為在 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í)行..."); } } }
圖片
因為是在 ScanForFinalization 之后做的判斷,這時候可能會存在 對象復(fù)活 的情況,所以垃圾又變成不垃圾了,如果是這種情況就不能切斷,參考代碼如下:
static void WeakReferenceCase(){ var person = new Person() { ressurect = true }; weakHandle = GCHandle.Alloc(person, GCHandleType.WeakTrackResurrection);}
圖片
在 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;}
源碼中有幾個注意點:
gc 在標(biāo)記時,將有根的對象mt的第一位設(shè)為 1 來表示當(dāng)前已經(jīng)標(biāo)記過,即有用對象,未被標(biāo)記的即為垃圾對象。
從簡化的源碼看,一旦有垃圾對象被送入到 終結(jié)器隊列的 預(yù)備區(qū) 時,就會通過 GCToEEInterface::EnableFinalization(true) 啟動終結(jié)器線程,所以在測試代碼中加了 GC.WaitForPendingFinalizers(); 就是為了等待終結(jié)器線程執(zhí)行完畢然后才判斷 Target,這樣結(jié)果就會更加準(zhǔn)確。
有些朋友會好奇那個 weakHandle.Target=null 的邏輯到底在 coreclr 的何處,這個比較簡單,可以用 windbg 下 ba 斷點即可,我們還是拿弱引用來舉例,截圖如下:
圖片
WeakReference 的內(nèi)部玩法有很多,更深入的理解還需要對 g_HandleTableMap 進行深度挖掘,后面有機會再聊吧,有時候dump分析還是挺苦逼的,需要對相關(guān)領(lǐng)域底層知識有一個足夠了解,否則談何修復(fù)呢?
本文鏈接:http://www.tebozhan.com/showinfo-26-97278-0.html聊一聊 C# 弱引用底層是怎么玩的
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com