[译].NET 4 中的 Background & Foreground GC

原文地址 : http://blogs.msdn.com/salvapatuel/archive/2009/06/10/background-and-foreground-gc-in-net-4.aspx

CLR 4 中另一个很有意思的新特性来自于 GC 团队。在新版本中,GC 团队对内存分配过程做了一些性能增强。通常,我们把这个新特性叫做“Background GC”。既如此,这家伙究竟在背后搞什么呢?

在 64 位处理器上,当应用程序启动时会消耗更多内存。而且还会在一个更大的地址空间进行移动。这样,当 GC 全速运行时,我们就能看到一些内存分配的延迟问题。或许你还记得 CLR 工作站版本使用的是 Concurrent GC 吧。这意味着 GC 线程将并行运行且不会阻塞应用程序执行(Well,我们当然会尽量缩减阻塞时间)。为了标记已死亡对象,GC 线程还会扫描第 2 代对象。如果已分配的内存相当大的话,这个操作会花费一定的时间,并且导致应用程序暂停。OK,现在你或许会问这样做究竟是什么意思?让我画几张图来仔细解释一下。

先分析下目前的 Concurrent GC 如何工作,我们应当如何改善它 :

GC1 现在,假设我们的应用程序需要执行全面垃圾回收。这样,GC 就会扫描第2代对象,并尽量把已死亡对象标记成自由对象。下面是一个简化步骤 :

1)GC 开始标记对象,检查堆栈和 GC 根。这个操作允许进一步分配,也就是说,你的程序此时可以创建新的对象。GC 将会把它们分配在第 0 代。

GC2

2)接下来,GC 会挂起 EE(执行引擎),这样应用程序的所有线程都会暂停。这个阶段不允许分配任何新对象。你的程序可能会稍有延迟。

3)EE 重新取得控制权,继续工作。这个阶段,允许内存分配。但是假如垃圾回收时 ephemeral segment 已经满了呢。这样将会怎样?

GC3

4)在这个阶段,即时回收不能进行内存交换。内存分配亦会被延后。同时增加了程序的延迟。

正如你所看到的,我们的问题是单一的 GC 线程不能同时应付两个操作。目前的 ephemeral segment 是 16MB(注意:将来这个数值可能会改变,不要依赖这个数值)。也就是说,一次最多分配 16MB。而我们的示例就是在 GC 回收结束之前,空间已经被消耗殆尽。我希望现在你能明白假如没有一个合适的理由,为什么我们不建议直接调用 GC.Collect() 方法了吧。

好了。现在让我来隆重介绍一下 Background GC 。这个模型正是为上面描述的场景而引入。它被用来优化缩减延迟时间。这个方案的灵感来自于创建一个 Background GC 来解决前述场景。而 Foreground GC 则只会在 2 代对象回收时,内存需要被回收时才会被触发。

现在,假如我们重复前述场景。我们在 Background GC 标记对象时,尝试分配内存。这时,Foreground GC 将会执行即时回收工作。

GC4

Foreground GC  线程将会标记已死亡对象和交换页面(这要比拷贝对象到第 2 代高效的多)。自由空间和已分配的对象会变成 2 代对象。

GC5

现在你看内存分配可以进行下去了。你的程序也不需要再等待 GC 全面回收工作结束了。

注意:这个方案只能在 CLR 工作站版本可用。而服务器版本则是每个核心一个阻塞 GC。我们还没有足够的时间来把它移植进去。但是在近期已有明确的移植计划。而且我们已经在 64 核 CPU 测试过这个方案。更客观的事实是它已经被 SQL 团队在 128 核上证实。

posted @ 2009-12-07 10:55 Angel Lucifer 阅读(...) 评论(...) 编辑 收藏