【问题标题】:What are the different heaps in .net?.net 中有哪些不同的堆?
【发布时间】:2015-10-14 09:42:54
【问题描述】:

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

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

【问题讨论】:

  • 这可能与 C# 垃圾收集器的工作方式有关。我认为这是一个分代 GC,它根据数据的最近使用情况来分离数据。
  • 有3个堆+大对象堆。所有对象都分配在堆 0 中。如果它们在一次垃圾收集中幸存下来,它们将被提升到堆 1,然后到堆 2,然后一直留在那里直到被收集。大对象堆 (LOH) 用于大小为 85000 字节或更大的对象(不是总的,连续的,如数组)。我不知道你从哪里得到第五堆。
  • Eric Lippert 写了一篇很棒的博客文章(分为两部分),介绍了堆栈和堆以及它的工作原理。看这里:blogs.msdn.com/b/ericlippert/archive/2009/04/27/…
  • 不确定这个问题的否决/关闭投票是为了什么 - 似乎很清楚,客观上可以回答,并且对于 SO 有答案将是一个非常有用的问题。不过,它可以做一些重新定位。

标签: c# .net memory-management garbage-collection heap-memory


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

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

    根据MSDN

    堆被组织成几代,因此它可以处理长寿命和短寿命的对象。垃圾回收主要发生在对通常只占用一小部分堆的短期对象的回收中。

    • 第 0 代。这是最年轻的一代,包含短期对象。短期对象的一个​​示例是临时变量。垃圾收集在这一代中发生的频率最高。 新分配的对象形成了新一代的对象,并且隐含地是第 0 代集合,除非它们是大对象,在这种情况下,它们会在第 2 代集合中的大对象堆上继续。 大多数对象在第 0 代中被回收以进行垃圾回收,并且不会存活到下一代。
    • 第 1 代。这一代包含短期对象,用作短期对象和长期对象之间的缓冲区。
    • 第 2 代。这一代包含长寿命对象。长生命周期对象的一个​​示例是服务器应用程序中的对象,其中包含在进程期间处于活动状态的静态数据。

    在垃圾回收中未被回收的对象称为幸存者,并被提升到下一代。

    重要的数据很快就会被垃圾收集器(更高代)放到次要位置,并减少检查删除的频率。这减少了检查真正需要持久化的内存所浪费的时间,这让您可以看到高效垃圾收集器带来的性能提升。

    【讨论】:

    • 这并不能解释为什么会有不同的堆,每个堆都有代。
    【解决方案3】:

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

    大对象堆 (LOH)

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

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

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

    每个小对象 (

    即使在下面执行时,实际上也会发生上述过程:(不需要手动调用)

    // 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 会抛出内存不足异常。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-08
      • 2022-12-10
      • 2018-09-02
      • 1970-01-01
      • 2011-11-20
      • 2023-03-09
      • 1970-01-01
      • 2011-01-15
      相关资源
      最近更新 更多