代码改变世界

.net垃圾回收学习[Back to Bascis: Generational garbage collection][翻译&&学习]

2011-08-25 07:16  一一九九  阅读(280)  评论(0编辑  收藏  举报

From: http://blogs.msdn.com/b/abhinaba/archive/2009/03/02/back-to-basics-generational-garbage-collection.aspx

     在mark-sweep garbage collection一文中提到mark-sweep garbage的算法的最大的缺点就是在整个heap需要标记和清除的时候引入了较大的系统暂停, 其中额一个重要的解决这个问题的优化措施就是引入generational garbage collection. 这个优化是基于以下几个观察的:

  • most objects die young
  • over 90% garbage collected in a gc is newly created post the previous gc cycle.
  • if an object survives a GC cycle the chances of it becoming garbage in the short term is low and hence the gc waster time marking it again and agin in each cycle.

      在以上的观察的基础进行的优化措施是将对象分割成为不同的Generations,对不同的Generation采取不同的回收频率。

      这个模式被证明是十分有效的,而且在现在的许多系统中被广泛的采用(包括.net).

Detailed algorithm

       对象可以根据不同的方式分割成为不同的Generation,比如说,根据创建的时间,然后,一个通用的方式是考虑将新创建的对象归为Generation 0, 然后假如在一个垃圾的回收周期中它没有被回收的话,它将被提升到下一个更高的Generation,Gen1, 类似的,如果一个Object 在Gen1中仍然没有被回收的话,它将被提升到Gen2。

      低等级的Generation中对象被回收的更频繁一些。这保证了较少的系统暂停,高等级的Generation回收很少被触发。

      究竟有多少Generation会被采用,不同的系统各不相同。在Net中,3 generation被使用。为了简单起便我们这里考虑2代系统,但是概念可以被扩展到2代以上。

      让我们将内存划为两个连续的内存块,一个是为Gen1使用,另一个为Gen0使用,最初的时候内存只从Gen0开始进行分配。如下图所示:

image

现在我们在Gen0中有4个对象,现在其中的一个引用对象被释放了。

image

现在假如GC被触发,它将采用Mark和Sweep方法在Gen0对象上并且清除掉两个不可达的对象,下面是经历了Clean Up后的最后结果:
image

剩存的两个Object被提升到了Gen1。 提升的过程包括复制两个对象到Gen1区域然后更新相关的引用。

image

现在假设经历了又一次的分配和释放,经过新的内存分配,Gen0已经变成了如下的样子:

image

分代的主要目的是为了降低需要Mark的对象的数量,所以第一个Root被用来Marking,因为它指向了一个Gen0对象。当使用第二个Root的时候,Marker发现引用是指向了Gen1对象,那么它不会继续跟着Reference下去标记,停止了Marking的过程。

现在我们只考虑将Gen0的对象进行标记,我们将标明的对象使用对号来标记。marking system将不会定位从Gen1到Gen0的引用(如上图的红线所示),那么有些对象的标记会被省去,导致该对象成为悬浮的指针。

一个解决这种能够情况的方案是以某种方式记录从Gen1到Gen0的所有的引用(将在下一个章节中讲述),然后使用这些对象作为Marking阶段的新的roots. 假如我们使用这种方法的话,我们会得到的心得标记过的对象。如下图所示:

image

现在将会得到标记后的对象。在另外一次GC后,将存活的对象提升到更高的等级,我们得到如下的图:

image

随后上述的一个过程将循环进行。

Tracking higher to lower generation references

       在通常的程序中只有很少的这种类型的引用(研究表明有小于总引用的…%1),然而,他们仍然需要被记录,下面是常见的两种方法:

Write barrier + card-table

      首先一张叫做Card Table的表被创建。This is essentially an array of bits. 每一位表明给定的内存空间是否是Dirty的(包含写入了一个低等级的对象)。比如说我们可以使用一位标记4KB的块。

image 

无论引用赋值何时在用户的代码中发生,除了直接进行赋值的操作外,它会被重新定向到一个很小的转换程序(.net和JITer采用这种方式)。 The Thunk 比较被赋值给的引用是否在GEN1的地址空间中。假如在其中的地址空间,随后Thunk更新Card table中相应的位,标明在那个范围内有一个Bit被覆盖为Dirty的(如上图的红色)。

如你所见,4KB的内存块的主要作用是为了优化比便降低CardTable的大小。 假如我们增大每个对象的颗粒度我们能够降低Marking的时间,但是我们Card table将会迅速的增大。

一个副作用是Thunk使得引用赋值变得慢了。

HW Support

      硬件支持也采用Card table, 但是仅仅使用HW+Os为Dirty Writes放出来特殊特性来代替使用Thunk。 比如说可以使用Win32 APi  GetWriteWatch 来获取写操作在什么地方发生,使用什么样的信息得到Cart Table的Pages。

      然而,这种支持并不是在所有的平台上都能够得到,因此很少被采用。