GC.Collect on only generation 2&大对象堆

时间:2009-09-23 22:23:48

标签: .net garbage-collection large-object-heap

在我的应用程序中,有一个特定的时间,一次释放大量的大对象。那时我想特别针对大对象堆(LOH)进行垃圾收集。

我知道你不能这样做,你必须调用GC.Collect(2),因为只有在进行第2代收集时才会在LOH上调用GC。但是,我在文档中读到,调用GC.Collect(2)仍会在第1代和第0代运行GC。

是否可以强制GC 收集第2代,而不包括第1代或第0代?

如果不可能,GC是否有理由以这种方式设计?

4 个答案:

答案 0 :(得分:14)

这是不可能的。 GC的设计使得第2代集合也始终收集第0代和第1代。

修改:在a GC developer's blog上找到了这个来源:

  

Gen2 GC需要完整的收藏   (Gen0,Gen1,Gen2和LOH!大   每个Gen2 GC都有GC对象   即使GC没有被触发   LOH缺乏空间。请注意那里   不是只收集大量的GC   对象。)需要更长的时间   年轻一代的收藏品。

编辑2 :来自同一博客的使用GC Efficiently Part 1Part 2显然Gen0和Gen1集合与Gen2集合相比很快,因此似乎合理我只会做Gen2不会有太大的性能优势。可能有一个更根本的原因,但我不确定。也许答案出现在该博客的一些文章中。

答案 1 :(得分:6)

由于所有新分配(大型对象除外)总是进入Gen0,因此GC设计为始终从指定的一代及以下收集。当您致电GC.Collect(2)时,您告诉GC从Gen0,Gen1和Gen2收集。

如果你确定你正在处理许多大对象(在分配时足够大以放置在LOH上的对象),最好的选择是确保你将它们设置为null(在VB中为Nothing)你完成了他们。 LOH分配尝试智能和重用块。例如,如果您在LOH上分配了一个1MB的对象,然后将其丢弃并将其设置为null,那么您将留下1MB的“空洞”。下次在LOH上分配大小为1MB 或更小的任何内容时,它将填入该孔(并继续填充它,直到下一个分配太大而无法容纳剩余空间,此时它将分配一个新的区块。)

请记住,.NET中的代数不是物理内容,而是逻辑上的分离,有助于提高GC性能。由于所有新分配都在Gen0中,因此始终是第一代要收集的。每个运行的收集周期,在收集后幸存的较低代的任何东西都被“提升”到下一代(直到达到Gen2)。

在大多数情况下,GC不需要超越收集Gen0。 GC的当前实现能够同时收集Gen0和Gen1,但是在收集Gen0或Gen1时它无法收集Gen2。 (.NET 4.0大大放宽了这个约束,在大多数情况下,GC能够收集Gen2,而Gen0或Gen1也被收集。)

答案 2 :(得分:0)

回答“为什么”这个问题:在物理上,没有Gen0和Gen1或Gen2这样的东西。它们都在虚拟地址空间上使用相同的内存块。它们之间的区别实际上只是通过绕着假想的边界限制来实现。

每个(小)对象都是从Gen0堆区域分配的。如果 - 在收集之后 - 它幸存下来,它会“向下”移动到托管堆块的那个区域,最终只是从垃圾堆中释放出来。这是通过压缩堆来完成的。完整收集完成后,Gen1的新“边框”将被设置为幸存对象之后的空间。

因此,如果你想出去尝试清除Gen0和/或Gen1,你就会在堆中打开漏洞,这些漏洞必须通过压缩“完整”堆来关闭 - 甚至是Gen0中的对象。显然这不会有任何意义,因为大多数这些对象无论如何都是垃圾。移动它们是没有意义的。没有必要在(否则压缩)堆上创建和留下大洞。

答案 3 :(得分:0)

每当系统执行特定代的垃圾收集时,它必须检查可能包含对该代的任何对象的引用的每个对象。在许多情况下,旧对象只能保存对其他旧对象的引用;如果系统正在执行Gen0集合,它可以忽略任何仅保存对Gen1和/或Gen2的引用的对象。同样,如果它正在执行Gen1集合,它可以忽略任何仅保留对Gen2的引用的对象。由于检查和标记对象代表了垃圾收集所需的大部分时间,因此能够跳过旧对象完全可以节省大量时间。

顺便提一下,如果您想知道系统如何“知道”对象是否可以保存对较新对象的引用,则系统会有特殊代码在对象的写入中设置每个对象描述符中的几个位。第一个位在每个垃圾收集时被重置,如果它仍然在下一次垃圾收集时被重置,系统将知道它不能包含对Gen0对象的任何引用(因为当对象上次写入时存在的任何对象都不存在由前一个集合清除的将是Gen1或Gen2)。第二个位在每个Gen1垃圾收集中重置,如果它仍然在下一个Gen1垃圾收集中重置,系统将知道它不能包含对Gen0或Gen1对象的任何引用(它保存引用的任何对象现在是Gen2) 。请注意,系统不知道或不关心写入对象的信息是否包含Gen0或Gen1引用。写入未标记对象时所需的陷阱是昂贵的,并且如果必须在每次写入对象时处理它,将极大地妨碍性能。为了避免这种情况,每当发生任何写入时都会标记对象,以便在下一次垃圾收集之前的任何其他写入可以不间断地继续进行。