6

.Net7 GC标记阶段代码的改变 - 江湖评谈

 1 year ago
source link: https://www.cnblogs.com/tangyanzhi1111/p/17226410.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

.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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK