CLR探索系列:GC 中的Card table和Brick Table(垃圾回收系列)
在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里面的状态信息。
posted on 2008-02-22 09:05 lbq1221119 阅读(3373) 评论(7) 编辑 收藏 举报