在CLR的垃圾回收子系統中,Card Table和Brick Table是兩個比較有意思的表。
在GC的過程中,一個Heap在運行了一段時間以後,已經分配的空間就會越來越大。在進行了一次局部代或者是完全的垃圾回收以後,就會涉及到一個GC堆的類似碎片整理的概念。整理優化一次GC Heap。同時,這種機制保證了譬如一個IIS Server在長時間的運行過程中的穩定性並且優化了其內存管理。
這樣的好處是顯而易見的,但是采用這種解決方案帶來的問題也很容易想到:譬如一個存在於GC Heap裡面的"Small" Object被移動了,同時它也被其它的object引用。出現了這種情況,在進行GC的時候,就需要遍歷整個GC的各種table,root,heap以及保留塊來搜索對這個對象的引用,然後更新這個引用。
這是一個相當消耗CPU資源的動作,幸運的是,GC使用了Card table來減小這個動作帶來的系統資源消耗。Card Table縮小了在這個搜索遍歷的過程中需要遍歷的范圍。
Card table實際上是一塊連續的內存區域,做為一個index,這塊內存區域裡面的每一個bit,都代表了GC Heap中的一塊連續的區域,這些bits就組成了一個card table。
CLR的實現中,Card Table中的一個bit就代表了GC heap裡面的128byte的區域。同時,對Card Table的update的動作是以byte為單位的。這就造成了每一次對Card Table的Update,影響到的其實是128*8=1kb這麼大的一個區域。
在DotNet的中間層,但凡涉及到修改一個Object的Ref的CIL Opcodes, 不僅僅會執行份內的修改ref的事情,同時還會很小心的update這個Card Table。
基於這種設計,在GC的時候,就可以先查找Card Table來看看哪些Object的Ref被修改了,然後只是針對這些區域去進行搜索遍歷來Update GC Heap中其它的地方對這個對象的引用。
這些Bit位的Update,可以由WriteBarrierHelper和ErectWriteBarrier 這兩個方法來完成:
//update一個Card table的方法。void GCHeap::ErectWriteBarrier(OBJECTREF *dst, OBJECTREF ref)
{
// 檢查dst的地址是不是在heap之內的地址。
if (((*(BYTE**)&dst) < g_lowest_address) || ((*(BYTE**)&dst) >= g_highest_address))
return;
if((BYTE*) OBJECTREFToObject(ref) >= g_ephemeral_low
&& (BYTE*) OBJECTREFToObject(ref) < g_ephemeral_high)
{
size_t card = gcard_of((BYTE*)dst);
BYTE* pCardByte = ((BYTE*) g_card_table) + card / CARDS_PER_BYTE;
//每一次update是以一個byte為單位的。
BYTE bitMask = (BYTE) (1 << (card % 8));
if( !((*pCardByte) & bitMask) )
{
//如果這個有ref被修改,那麼這個card的一個byte裡面都被置為1
*pCardByte = 0xFF;
}
}
}
同時,在update一個bit的時候,還使用了一種叫做wirte barrier的技術。這種技術,在程序修改一個ref的內容的時候,可以被編譯器得知。這個技術在card update裡面,具體到某個平台上面是一段匯編的代碼,其實個人認為就是對CIL代碼的一個擴展。
Bricks和card差不多。它的最主要的作用,是Collector用來定位一個Object在Heap裡面的位置。它的表示形式,是一個16位有符號int類型的數組。每一個單元叫做brick slot,cover2048byte的內存區域。對於每一個Brick slot,每一個16bit的成員都可以有三種表現形式:
1.十六位的正數表示偏移。
2.一個負數表示相對於Brick table本身的唯一。
3.保留值,做為一個標志位來使用。
另外,這兩種結構,都不能保證完全表現一個Heap裡面的狀態信息。