9

在我的应用程序中,有一个特定时间会同时释放多个大型对象。那时我想专门对大对象堆(LOH)进行垃圾收集。

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

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

如果不可能,是否有理由这样设计 GC?

4

4 回答 4

14

这是不可能的。GC 被设计为使第 2 代收集始终同时收集第 0 代和第 1 代。

编辑:在GC 开发人员的博客上为您找到了一个来源:

Gen2 GC 需要完整的收集(Gen0、Gen1、Gen2 和 LOH)!即使 GC 不是由于 LOH 中的空间不足而触发的,每次 Gen2 GC 都会对大对象进行 GC。请注意,没有只收集的 GC大对象。)这比年轻一代集合花费的时间要长得多。

编辑 2:来自同一个博客的高效使用 GC第 1部分和第 2 部分显然 Gen0 和 Gen1 集合与 Gen2 集合相比速度更快,因此在我看来,仅执行 Gen2 不会带来太多性能优势。可能有更根本的原因,但我不确定。也许答案就在该博客的一些文章中。

于 2009-09-23T22:25:09.450 回答
6

由于所有新的分配(除了大对象)总是在 Gen0 中进行,GC 被设计为总是从指定的代及以下进行收集。当您调用 时GC.Collect(2),您是在告诉 GC 从 Gen0、Gen1 和 Gen2 收集。

如果您确定要处理大量大型对象(在分配时对象大到足以放置在 LOH 上),那么最好的选择是确保在完成后将它们设置为 null(VB 中的无)跟他们。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。)

于 2009-09-23T22:58:04.190 回答
0

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

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

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

于 2011-01-27T08:41:00.740 回答
0

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

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

于 2011-06-24T15:06:21.537 回答