【问题标题】:Large Object Heap fragmentation: CLR has any solution to it?大对象堆碎片:CLR有什么解决办法吗?
【发布时间】:2011-07-12 03:00:07
【问题描述】:

如果您的应用程序必须对大尺寸对象(>85000 字节)进行大量分配/取消分配,最终会导致内存碎片,您的应用程序将抛出内存不足异常。

这个问题有什么解决办法还是CLR内存管理的限制?

【问题讨论】:

  • 请注意,大对象堆阈值是每个 85,000 字节的分配,而不是每个 85 字节。如果这是一个错字,建议编辑您的问题。 (如果不是,请重新考虑这个问题)

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


【解决方案1】:

我们在多个线程中处理图像。由于图像足够大,这也会由于内存碎片而导致 OutOfMemory 异常。我们试图通过使用不安全的内存和为每个线程预分配堆来解决这个问题。不幸的是,这并没有完全帮助,因为我们依赖于几个库:我们能够在代码中解决问题,但不是第 3 方。

最终我们用进程代替了线程,让操作系统来完成繁重的工作。操作系统早就构建了内存碎片的解决方案,因此忽略它是不明智的。

【讨论】:

    【解决方案2】:

    我在另一个答案中看到 LOH 可以缩小尺寸:

    Large Arrays, and LOH Fragmentation. What is the accepted convention?

    " ... 现在,话虽如此,如果 LOH 末端的区域完全没有活动对象,则 LOH 的大小可能会缩小,因此唯一的问题是如果您将对象长时间留在其中(例如应用程序的持续时间)。 ... "

    除此之外,您还可以让您的程序在 32 位系统上以高达 3GB 的扩展内存运行,在 64 位系统上以高达 4 GB 的内存运行。 只需在链接器或此构建后事件中添加标志 /LARGEADDRESSAWARE:

    调用“$(DevEnvDir)..\tools\vsvars32.bat” editbin /LARGEADDRESSAWARE "$(TargetPath)"

    最后,如果您计划长时间运行程序并使用大量大对象,您将不得不优化内存使用,您甚至可能不得不重用分配的对象以避免垃圾收集器,这在概念上是相似的,使用实时系统。

    【讨论】:

      【解决方案3】:
      Is there any solution to this problem or is it a limitation of CLR memory management?
      

      除了重新考虑您的设计之外,没有其他解决方案。这不是 CLR 的问题。请注意,非托管应用程序的问题是相同的。事实是,应用程序同时使用了太多内存,并且在内存中放置“不利”的部分。尽管如此,如果必须指出一些外部罪魁祸首,我宁愿指向 OS 内存管理器,它(当然)不会压缩其 vm 地址空间。

      CLR 在空闲列表中管理 LOH 的空闲区域。在大多数情况下,这是对抗碎片化的最佳方法。但是由于对于非常大的对象,每个 LOH 段的对象数量会减少 - 我们最终每个段只有一个对象。这些对象在虚拟机空间中的位置完全取决于操作系统的内存管理器。这意味着,碎片主要发生在操作系统级别 - 而不是 CLR。这是堆碎片的一个经常被监督的方面,它不是 .NET 的责任。 (但这也是事实,碎片也可能发生在托管端,就像在that article 中很好地展示的那样。)

      常见的解决方案已经命名:重用您的大对象。到目前为止,我还没有遇到任何通过适当的设计无法做到的情况。但是,有时它可能会很棘手,因此可能很昂贵。

      【讨论】:

        【解决方案4】:

        CLR 的垃圾收集器中的大对象的问题在于它们在不同的堆中进行管理。 垃圾收集器使用一种称为“压缩”的机制,它基本上是常规堆中对象的碎片和重新链接。 问题是,由于“压缩”大对象(复制和重新链接它们)是一个昂贵的过程,GC 为它们提供了一个不同的堆,永远不会被压缩。

        还要注意内存分配是连续的。这意味着如果您分配对象 #1,然后分配对象 #2,对象 #2 将始终放置在对象 #1 之后。

        这可能是导致您出现 OutOfMemoryExceptions 的原因。

        我建议看看 Flyweight、Lazy Initialization 和 Object Pool 等设计模式。

        您也可以强制 GC 收集,如果您怀疑其中一些大对象已经死亡并且由于您的控制流程中的缺陷而没有被收集,导致它们在准备好收集之前到达更高代.

        【讨论】:

        • 大对象不跟踪世代,它们仅在完整收集期间被收集。
        • “意思是如果你分配对象#1然后对象#2,对象#2将总是放在对象#1之后” - 澄清:如果你先释放对象#1,它的大小是>= 对象#2,则 CLR 可能会将其分配到对象#1 先前使用的空间中。
        【解决方案5】:

        程序总是在 OOM 上发生炸弹,因为它要求的内存块太大,而不是因为它完全耗尽了所有虚拟内存地址空间。您可能会说这是 LOH 碎片化的问题,同样容易争论该程序使用了过多的虚拟内存。

        一旦程序超出了分配一半可寻址虚拟内存(千兆字节)的范围,就真的是时候考虑让其代码更智能,以免占用太多内存。或者将 64 位操作系统作为先决条件。后者总是更便宜。它也不会从你的口袋里掏出来。

        【讨论】:

        • 在 64 位操作系统上完全同意你的看法,但是我希望我所有的客户都愿意根据这个理由升级他们的计算机 :) 我们生活在一个艰难的世界中
        【解决方案6】:

        不幸的是,我所见过的所有信息都只建议您自己管理风险因素:重用大型对象,在开始时分配它们,确保它们的大小是彼此的倍数,使用替代数据结构(列表, 树) 而不是数组。这只是给了我另一个想法,即创建一个非分段列表,而不是一个大数组,而是拆分成更小的数组。数组/列表似乎是 IME 最常见的罪魁祸首。

        这是一篇关于它的 MSDN 杂志文章: http://msdn.microsoft.com/en-us/magazine/cc534993.aspx,不过没什么用处。

        【讨论】:

          猜你喜欢
          • 2010-10-15
          • 1970-01-01
          • 2011-12-22
          • 2011-11-14
          • 2012-08-01
          • 2012-04-03
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多