在.NET中有哪些不同的堆?

11

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

我想知道每个堆的作用及通常存储哪些内容的解释。


这可能与C#垃圾回收器的工作方式有关。我认为它是一种分代GC,根据数据最近使用的时间进行分离。 - ryanyuyu
2
有3个堆+大对象堆。所有对象都分配在堆0中。如果它们经过一次垃圾回收后幸存下来,它们就会晋升到堆1,然后到堆2,并且一直停留在那里直到被回收。大对象堆(LOH)用于大小为85000字节或更大的对象(不是总大小,而是连续的,如数组)。我不知道你从哪里得到第五个堆。 - Lasse V. Karlsen
可能会感兴趣:https://dev59.com/P2gu5IYBdhLWcg3wnoaC - Davin Tryon
Eric Lippert写了一篇关于堆栈和堆的工作原理的精彩博客文章(分为两部分)。请看这里:http://blogs.msdn.com/b/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx - Icemanind
3
不确定对这个问题的踩/关闭投票是为什么 - 看起来已经足够清晰,可客观回答并且在SO上获得答案将非常有用。不过它需要一些重新定位。 - Ant P
显示剩余3条评论
3个回答

20

其他答案似乎忽略了堆和代之间的区别。我不明白为什么商业分析工具会混淆这两个概念,所以我强烈怀疑实际上是堆而不是代。

当CLR GC使用服务器模式时,它为进程亲和掩码中的每个逻辑处理器创建一个单独的堆。这种拆分主要是为了提高分配的可伸缩性,并在并行GC中执行。这些是单独的内存区域,但你当然可以在堆之间具有对象引用,并将它们视为单个逻辑堆。

因此,假设您有四个逻辑处理器(例如启用HyperThreading的i5 CPU),则在服务器GC下会有四个堆。

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


这些是独立的内存区域,但你当然可以在堆之间拥有对象引用,并将它们视为单个逻辑堆。- 我正在寻找关于这些堆之间访问如何工作的更多信息,你有更多信息可以分享吗? - Daniel Genezini

8
由于C#垃圾回收器的工作方式,您拥有不同的堆。它使用分代GC,根据数据最近使用的时间将数据分离。使用不同的堆允许垃圾回收器更有效地清理内存。
根据MSDN
堆栈按代进行组织,以处理长期存在和短期存在的对象。垃圾回收主要发生在短期存在的对象被回收时,这些对象通常只占用堆栈的一小部分。
以下是三个代的内容:
- 第0代:最年轻的一代,包含短期存在的对象。临时变量就是一个短期存在的对象的例子。大多数对象在第0代进行垃圾回收,并且不会存留到下一代。 - 第1代:包含短期存在的对象,是短期存在的对象和长期存在的对象之间的缓冲区。 - 第2代:包含长期存在的对象。服务器应用程序中包含静态数据并且在进程持续时间内保持活动状态的对象是长期存在的对象的一个例子。
未在垃圾回收中回收的对象称为幸存者,并晋升到下一代。
重要数据很快就被放在垃圾收集器的后台(更高一代)并且检查删除的频率较低。这降低了检查确实需要持久化内存的时间,从而使您从高效的垃圾收集器中获得性能提升。

4
这并不解释为什么会有不同的堆,每个堆都有代。 - Thomas Weller
在.NET 5中添加了POH(固定对象堆),有一篇很棒的文章介绍了它的内部工作原理(以及SOH和LOH的内容)- https://devblogs.microsoft.com/dotnet/internals-of-the-poh/ - Eugene Zakharov

1

在管理对象方面,有三个小对象堆(SOH)和一个大对象堆(LOH)。

大对象堆(LOH)

大于85KB的对象将直接进入LOH。如果您有太多的大对象,则存在一些风险。这是另一个讨论话题,有关详细信息,请查看大对象堆的危险性

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

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

每个小对象(<85KB)最初都会进入Gen0。当Gen0已满时,垃圾回收仅针对Gen0执行。它检查所有在Gen0中的实例,并清除/释放任何不必要对象(非引用,超出作用域或已处理的对象)使用的内存。然后将所有所需(正在使用)实例复制到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);

当垃圾回收在一个阶段一直执行此操作时,如果第二代堆溢出,它会尝试清理第二代堆。

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

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接