.Net7 GC標(biāo)記階段代碼的改變
前言
由于業(yè)務(wù)需求,在探究.Net7的CLR,發(fā)現(xiàn)了一個不通的地方,也就是通過GCInfo獲取到了對象之后。它并沒有在GcScanRoots(對象掃描標(biāo)記)里面對它進行標(biāo)記,那么如果沒有標(biāo)記這個對象如何被計劃階段構(gòu)建呢?仔細研讀,發(fā)現(xiàn)它跟之前的代碼之所以不同,是因為它把標(biāo)記抽取出來,另外形成一個數(shù)組循環(huán)標(biāo)記。本篇來看下。
概括
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();
}
調(diào)用GC.Collect()函數(shù),GC垃圾回收的第一步,就是標(biāo)記。這個標(biāo)記實質(zhì)上可以分為以下幾步。
一:獲取到所有的線程(GetAllThreadList)
二:遍歷循環(huán)這些線程的幀
三:通過遍歷到的幀,找到這些幀對應(yīng)的GCInfo
四:通過GCInfo的偏移量和寄存器找到相對應(yīng)的對象
五:對找到的對象進行標(biāo)記。
以上四步,基本上沒變。第五步標(biāo)記的時候,它加入了一些新的代碼。
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];// 這里把這個索引的值從數(shù)組取出來
? ?slot_table[slot_index] = o;//把新對象賦值到索引所在的數(shù)組內(nèi)存
? ?curr_slot_index = (slot_index + 1) % slot_count; ? ?if (old_o == nullptr)//這個地方是關(guān)鍵,因為假如說你按照上面的示例代碼,之前并沒有這個PM對象。所以這個old_o是等于nullptr的,所以它直接return了。那么下面就不存在標(biāo)記了。問題是這個標(biāo)記不標(biāo)記??還是在別的地方標(biāo)記了??
? ? ? ?return nullptr;
? ?BOOL already_marked = marked (old_o); ? ?if (already_marked)
? ?{ ? ? ? ?return nullptr;
? ?} ? ?set_marked (old_o); ? ?return old_o;
}
二:解決
要解決這個問題,就需要知道數(shù)組slot_table里面的數(shù)值是何時被變動的。這個其實很簡單在VS里面可以,打個條件斷點--值更改時中斷。
結(jié)果發(fā)現(xiàn)在函數(shù)get_next_marked里面對slot_table數(shù)組里面的值(也就是對象)進行了一個標(biāo)記
uint8_t* mark_queue_t::get_next_marked(){ ? ?size_t slot_index = curr_slot_index; //獲取到當(dāng)前slot_table數(shù)組的總數(shù)索引
? ?size_t empty_slot_count = 0;
? ?while (empty_slot_count < slot_count) //從零開始循環(huán)總數(shù)索引
? ?{ ? ? ? ?uint8_t* o = slot_table[slot_index]; //一個個的取出來,保存到o變量。
? ? ? ?slot_table[slot_index] = nullptr; //然后把相應(yīng)的索引位內(nèi)存置0,以便下次標(biāo)記的時候繼續(xù)使用。
? ? ? ?slot_index = (slot_index + 1) % slot_count; ? ? ? ?if (o != nullptr) //如果這個o不等于null,那么下面的代碼就是對它進行一個標(biāo)記。
? ? ? ?{
? ? ? ? ? ?BOOL already_marked = marked (o); //判斷它是否被標(biāo)記
? ? ? ? ? ?if (!already_marked) // 如果沒有
? ? ? ? ? ?{
? ? ? ? ? ? ? ?set_marked (o); // 則對它進行標(biāo)記
? ? ? ? ? ? ? ?curr_slot_index = slot_index;
? ? ? ? ? ? ? ?return o;//把標(biāo)記過的對象返回
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?empty_slot_count++;//繼續(xù)循環(huán)下一次,繼續(xù)標(biāo)記下一個
? ?} ? ?return nullptr;// 如果索引為空,則直接返回null}
這個函數(shù)是被drain_mark_queue函數(shù)調(diào)用,而前者則是在GCScanRoot整個函數(shù)被完成之后調(diào)用的。
那么整體的就打通關(guān)節(jié)了,實質(zhì)上它是抽取出來了,重新進行了標(biāo)記。而非在GCScanRoot里面進行標(biāo)記。