.net 中有哪些不同的堆?

What are the different heaps in .net?

我在 dotmemory 中分析 Windows Forms 应用程序的内存使用情况,我注意到我的应用程序有 0-4 个不同大小的堆以及大对象堆。

我只是想知道是否有人对每个堆的用途以及每个堆中通常存储的内容有很好的解释?

由于 C# 垃圾收集器的工作方式,您有不同的堆。它使用分代 GC,根据数据的最近使用时间来分离数据。使用不同的堆可以让垃圾收集器更有效地清理内存。

根据MSDN

The heap is organized into generations so it can handle long-lived and short-lived objects. Garbage collection primarily occurs with the reclamation of short-lived objects that typically occupy only a small part of the heap.

  • Generation 0. This is the youngest generation and contains short-lived objects. An example of a short-lived object is a temporary variable. Garbage collection occurs most frequently in this generation. Newly allocated objects form a new generation of objects and are implicitly generation 0 collections, unless they are large objects, in which case they go on the large object heap in a generation 2 collection. Most objects are reclaimed for garbage collection in generation 0 and do not survive to the next generation.
  • Generation 1. This generation contains short-lived objects and serves as a buffer between short-lived objects and long-lived objects.
  • Generation 2. This generation contains long-lived objects. An example of a long-lived object is an object in a server application that contains static data that is live for the duration of the process.

Objects that are not reclaimed in a garbage collection are known as survivors, and are promoted to the next generation.

重要数据很快就会被垃圾收集器搁置一旁(更高代),并且很少检查是否删除。这减少了浪费在检查真正需要保留的内存上的时间,这让您可以看到高效垃圾收集器带来的性能提升。

在托管对象方面,有 3 个小对象堆 (SOH) 和 1 个大对象堆 (LOH)。

大对象堆 (LOH)

大于 85KB 的对象将直接进入 LOH。如果您有太多大对象,则存在一些风险。这是一个不同的讨论,有关更多详细信息,请查看 The Dangers of the Large Object Heap

小对象堆 (SOH):Gen0、Gen1、Gen2

垃圾收集器使用巧妙的算法仅在需要时执行垃圾收集器。完整的垃圾收集过程是一项昂贵的操作,不应经常发生。因此,它已将其 SOH 分为三个部分,正如您所注意到的,每个 Gen 都有指定数量的内存。

每个小对象 (<85KB) 最初都进入 Gen0。当 Gen0 已满时,垃圾回收仅针对 Gen0 执行。它检查 Gen0 和 clears/releases 内存中的所有实例,这些实例被任何不必要的对象(未引用、超出范围或处置的对象)使用。然后它将所有需要的(使用中的)实例复制到 Gen1。

即使执行以下操作也会实际发生上述过程:(无需手动调用)

// Perform a collection of generation 0 only.
GC.Collect(0);

这样,垃圾收集器首先清除分配给短期实例的内存(不可变的字符串、方法中的变量或更小的范围)。

当GC在一个阶段一直做这个操作时,Gen1会溢出。然后对Gen1做同样的操作。它清除了 Gen1 中所有不需要的内存,并将所有需要的内存复制到 Gen2。

以上过程是您手动执行下面的过程(不需要手动调用)

// Perform a collection of all generations up to and including 1.
GC.Collect(1);

当 GC 在一个阶段继续执行此操作时,如果 Gen2 溢出,它会尝试清理 Gen2。

即使您手动执行以下操作(不需要手动执行)也会发生上述过程

// Perform a collection of all generations up to and including 2.
GC.Collect(2);

如果需要从 Gen1 复制到 Gen2 的内存量大于 Gen2 中可用的内存量,GC 将抛出内存不足异常。

其他答案似乎忽略了 heapsgenerations 之间存在差异的事实。我不明白为什么商业分析器会混淆这两个概念,所以我强烈怀疑它毕竟是堆而不是世代。

当 CLR GC 使用 server flavor 时,它会为进程的关联掩码中的每个逻辑处理器创建一个单独的堆。这种细分的原因主要是为了提高分配的可伸缩性,并在 GC 中并行执行。这些是独立的内存区域,但是您当然可以在堆之间有对象引用,并且可以将它们视为单个逻辑堆。

因此,假设您有四个逻辑处理器(例如启用了超线程的 i5 CPU),您将在服务器 GC 下有四个堆。

大对象堆有一个不幸的、令人困惑的名字。它不是与每个处理器堆相同意义上的堆。它是 多个 包含大对象的内存区域之上的逻辑抽象。