CLR探索系列:GC 中的Card table和Brick Table(垃圾回收系列)

       CLR的垃圾回收子系统中,Card TableBrick Table是两个比较有意思的表。

       GC的过程中,一个Heap在运行了一段时间以后,已经分配的空间就会越来越大。在进行了一次局部代或者是完全的垃圾回收以后,就会涉及到一个GC堆的类似碎片整理的概念。整理优化一次GC Heap。同时,这种机制保证了譬如一个IIS Server在长时间的运行过程中的稳定性并且优化了其内存管理。

      

       这样的好处是显而易见的,但是采用这种解决方案带来的问题也很容易想到:譬如一个存在于GC Heap里面的"Small" Object被移动了,同时它也被其它的object引用。出现了这种情况,在进行GC的时候,就需要遍历整个GC的各种tablerootheap以及保留块来搜索对这个对象的引用,然后更新这个引用。

       这是一个相当消耗CPU资源的动作,幸运的是,GC使用了Card table来减小这个动作带来的系统资源消耗。Card Table缩小了在这个搜索遍历的过程中需要遍历的范围。

 

       Card table实际上是一块连续的内存区域,做为一个index,这块内存区域里面的每一个bit,都代表了GC Heap中的一块连续的区域,这些bits就组成了一个card table

       CLR的实现中,Card Table中的一个bit就代表了GC heap里面的128byte的区域。同时,对Card Tableupdate的动作是以byte为单位的。这就造成了每一次对Card TableUpdate,影响到的其实是128*8=1kb这么大的一个区域。

      

       DotNet的中间层,但凡涉及到修改一个ObjectRefCIL Opcodes 不仅仅会执行份内的修改ref的事情,同时还会很小心的update这个Card Table

       基于这种设计,在GC的时候,就可以先查找Card Table来看看哪些ObjectRef被修改了,然后只是针对这些区域去进行搜索遍历来Update GC Heap中其它的地方对这个对象的引用。

 

       这些Bit位的Update,可以由WriteBarrierHelperErectWriteBarrier 这两个方法来完成:

//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代码的一个扩展。

 

       Brickscard差不多。它的最主要的作用,是Collector用来定位一个ObjectHeap里面的位置。它的表示形式,是一个16位有符号int类型的数组。每一个单元叫做brick slotcover2048byte的内存区域。对于每一个Brick slot,每一个16bit的成员都可以有三种表现形式:

1.         十六位的正数表示偏移。

2.         一个负数表示相对于Brick table本身的唯一。

3.         保留值,做为一个标志位来使用。

 

      

       另外,这两种结构,都不能保证完全表现一个Heap里面的状态信息。

posted on 2008-02-22 09:05  lbq1221119  阅读(3373)  评论(7编辑  收藏  举报

导航