自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

一、背景

1. 講故事

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

二、弱引用的玩法

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í)行之后判斷對象是否為垃圾對象,如果是的話直接切斷引用。

可能這么說有點(diǎn)抽象,畫張圖如下:

圖片圖片

2. 一個簡單的測試?yán)?/h3>

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

  • 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í)行...");
            }
        }
    }

圖片圖片

  • WeakTrackResurrection 的情況

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

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

圖片圖片

3. coreclr源碼分析

在 coreclr 里有一個 struct 枚舉強(qiáng)對應(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;
}

源碼中有幾個注意點(diǎn):

  • 如何判斷一個對象為垃圾

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

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

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

4. 切斷邏輯在哪里

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

圖片圖片

三、總結(jié)

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

責(zé)任編輯:武曉燕 來源: 一線碼農(nóng)聊技術(shù)
相關(guān)推薦

2025-01-10 08:15:22

C#異步底層

2024-08-26 14:46:57

2021-03-29 00:02:10

C#Attribute元素

2024-12-26 10:05:58

C#前臺線程

2023-10-24 15:56:23

2021-06-29 08:45:55

邏輯變量法函數(shù)

2023-03-05 18:40:39

iptables防火墻軟件

2022-08-30 07:39:57

C++namespace隔離

2024-10-08 10:55:04

2020-10-30 07:11:31

C 語言編程

2023-12-07 07:26:04

2022-11-02 08:51:01

2024-01-02 13:26:39

TLSC#線程

2020-10-23 07:00:00

C++函數(shù)

2023-07-06 13:56:14

微軟Skype

2020-09-08 06:54:29

Java Gradle語言

2023-09-22 17:36:37

2021-01-28 22:31:33

分組密碼算法

2020-05-22 08:16:07

PONGPONXG-PON

2018-03-22 10:36:15

未來數(shù)據(jù)中心停機(jī)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號