.net垃圾回收学习[Back to Bascis: Generational garbage collection][翻译&&学习]
2011-08-25 07:16 一一九九 阅读(280) 评论(0) 编辑 收藏 举报在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开始进行分配。如下图所示:
现在我们在Gen0中有4个对象,现在其中的一个引用对象被释放了。
现在假如GC被触发,它将采用Mark和Sweep方法在Gen0对象上并且清除掉两个不可达的对象,下面是经历了Clean Up后的最后结果:
剩存的两个Object被提升到了Gen1。 提升的过程包括复制两个对象到Gen1区域然后更新相关的引用。
现在假设经历了又一次的分配和释放,经过新的内存分配,Gen0已经变成了如下的样子:
分代的主要目的是为了降低需要Mark的对象的数量,所以第一个Root被用来Marking,因为它指向了一个Gen0对象。当使用第二个Root的时候,Marker发现引用是指向了Gen1对象,那么它不会继续跟着Reference下去标记,停止了Marking的过程。
现在我们只考虑将Gen0的对象进行标记,我们将标明的对象使用对号来标记。marking system将不会定位从Gen1到Gen0的引用(如上图的红线所示),那么有些对象的标记会被省去,导致该对象成为悬浮的指针。
一个解决这种能够情况的方案是以某种方式记录从Gen1到Gen0的所有的引用(将在下一个章节中讲述),然后使用这些对象作为Marking阶段的新的roots. 假如我们使用这种方法的话,我们会得到的心得标记过的对象。如下图所示:
现在将会得到标记后的对象。在另外一次GC后,将存活的对象提升到更高的等级,我们得到如下的图:
随后上述的一个过程将循环进行。
Tracking higher to lower generation references
在通常的程序中只有很少的这种类型的引用(研究表明有小于总引用的…%1),然而,他们仍然需要被记录,下面是常见的两种方法:
Write barrier + card-table
首先一张叫做Card Table的表被创建。This is essentially an array of bits. 每一位表明给定的内存空间是否是Dirty的(包含写入了一个低等级的对象)。比如说我们可以使用一位标记4KB的块。
无论引用赋值何时在用户的代码中发生,除了直接进行赋值的操作外,它会被重新定向到一个很小的转换程序(.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。
然而,这种支持并不是在所有的平台上都能够得到,因此很少被采用。