.Net7 GC標記階段代碼的改變

前言

由於業務需求,在探究.Net7的CLR,發現了一個不通的地方,也就是通過GCInfo獲取到了對象之後。它並沒有在GcScanRoots(對象掃描標記)裏面對它進行標記,那麼如果沒有標記這個對象如何被計劃階段構建呢?仔細研讀,發現它跟之前的代碼之所以不同,是因爲它把標記抽取出來,另外形成一個數組循環標記。本篇來看下。


概括

1.問題:
假如說有以下示例代碼:

static void Main(string[] args)
{
    Console.WriteLine("Tian Xia Feng Yun Chu Wo Bei!\r\n");
    Program PM= new Program();
    PM = null;
    GC.Collect();
}

調用GC.Collect()函數,GC垃圾回收的第一步,就是標記。這個標記實質上可以分爲以下幾步。
一:獲取到所有的線程(GetAllThreadList)
二:遍歷循環這些線程的幀
三:通過遍歷到的幀,找到這些幀對應的GCInfo
四:通過GCInfo的偏移量和寄存器找到相對應的對象
五:對找到的對象進行標記。

以上四步,基本上沒變。第五步標記的時候,它加入了一些新的代碼。

uint8_t *mark_queue_t::queue_mark(uint8_t *o)
{
    Prefetch (o);
    size_t slot_index = curr_slot_index; //這裏有一個slot的索引
    uint8_t* old_o = slot_table[slot_index];// 這裏把這個索引的值從數組取出來
    slot_table[slot_index] = o;//把新對象賦值到索引所在的數組內存
    curr_slot_index = (slot_index + 1) % slot_count;
    if (old_o == nullptr)//這個地方是關鍵,因爲假如說你按照上面的示例代碼,之前並沒有這個PM對象。所以這個old_o是等於nullptr的,所以它直接return了。那麼下面就不存在標記了。問題是這個標記不標記??還是在別的地方標記了??
        return nullptr;
    BOOL already_marked = marked (old_o);
    if (already_marked)
    {
        return nullptr;
    }
    set_marked (old_o);
    return old_o;
}

二:解決
要解決這個問題,就需要知道數組slot_table裏面的數值是何時被變動的。這個其實很簡單在VS裏面可以,打個條件斷點--值更改時中斷。
結果發現在函數get_next_marked裏面對slot_table數組裏面的值(也就是對象)進行了一個標記

uint8_t* mark_queue_t::get_next_marked()
{
    size_t slot_index = curr_slot_index; //獲取到當前slot_table數組的總數索引
    size_t empty_slot_count = 0; 
    while (empty_slot_count < slot_count) //從零開始循環總數索引
    {
        uint8_t* o = slot_table[slot_index]; //一個個的取出來,保存到o變量。
        slot_table[slot_index] = nullptr; //然後把相應的索引位內存置0,以便下次標記的時候繼續使用。
        slot_index = (slot_index + 1) % slot_count;
        if (o != nullptr) //如果這個o不等於null,那麼下面的代碼就是對它進行一個標記。
        {
            BOOL already_marked = marked (o); //判斷它是否被標記
            if (!already_marked) // 如果沒有
            { 
                set_marked (o); // 則對它進行標記
                curr_slot_index = slot_index; 
                return o;//把標記過的對象返回
            }
        }
        empty_slot_count++;//繼續循環下一次,繼續標記下一個
    }
    return nullptr;// 如果索引爲空,則直接返回null
}

這個函數是被drain_mark_queue函數調用,而前者則是在GCScanRoot整個函數被完成之後調用的。
那麼整體的就打通關節了,實質上它是抽取出來了,重新進行了標記。而非在GCScanRoot裏面進行標記。


結尾:

作者:江湖評談。公衆號:jianghupt。掃碼關注我,帶你瞭解高階技術,不侷限於.Net。
image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章