如果您的应用程序必须对大尺寸对象(> 85000 字节)进行大量分配/取消分配,最终会导致内存碎片,您的应用程序将抛出内存不足异常。
这个问题有什么解决方案,还是CLR内存管理的限制?
如果您的应用程序必须对大尺寸对象(> 85000 字节)进行大量分配/取消分配,最终会导致内存碎片,您的应用程序将抛出内存不足异常。
这个问题有什么解决方案,还是CLR内存管理的限制?
不幸的是,我所见过的所有信息都只建议自己管理风险因素:重用大对象,在开始时分配它们,确保它们的大小是彼此的倍数,使用替代数据结构(列表、树)而不是数组。这只是给了我另一个想法,即创建一个非分段列表,而不是一个大数组,而是拆分成更小的数组。数组/列表似乎是 IME 中最常见的罪魁祸首。
这是一篇关于它的 MSDN 杂志文章:http: //msdn.microsoft.com/en-us/magazine/cc534993.aspx,但它并没有太多用处。
CLR 的垃圾收集器中的大型对象的问题是它们在不同的堆中进行管理。垃圾收集器使用一种称为“压缩”的机制,它基本上是常规堆中对象的碎片和重新链接。问题是,由于“压缩”大对象(复制和重新链接它们)是一个昂贵的过程,GC 为它们提供了一个不同的堆,它永远不会被压缩。
另请注意,内存分配是连续的。这意味着如果您分配对象#1,然后分配对象#2,则对象#2 将始终放置在对象#1 之后。
这可能是导致您出现 OutOfMemoryExceptions 的原因。
我建议看一下 Flyweight、Lazy Initialization 和 Object Pool 等设计模式。
您也可以强制 GC 收集,如果您怀疑其中一些大型对象已经死亡并且由于您的控制流程中的缺陷而没有被收集,导致它们在准备好收集之前到达更高代。
一个程序总是在 OOM 上发生炸弹,因为它要求的内存块太大,而不是因为它完全耗尽了所有虚拟内存地址空间。您可能会争辩说这是 LOH 碎片化的问题,同样容易争辩说程序使用了过多的虚拟内存。
一旦程序超出了分配一半可寻址虚拟内存(千兆字节)的范围,就真的是时候考虑让其代码更智能,以免占用太多内存。或者将 64 位操作系统作为先决条件。后者总是更便宜。它也不会从你的口袋里出来。
Is there any solution to this problem or is it a limitation of CLR memory management?
除了重新考虑您的设计之外,没有其他解决方案。这不是CLR的问题。请注意,非托管应用程序的问题是相同的。事实是,应用程序同时使用过多的内存,并且在内存中放置“不利”的段。尽管如此,如果必须指出一些外部罪魁祸首,我宁愿指向 OS 内存管理器,它(当然)不会压缩其 vm 地址空间。
CLR 在空闲列表中管理 LOH 的空闲区域。在大多数情况下,这是对抗碎片化的最佳方法。但是由于对于非常大的对象,每个 LOH 段的对象数量会减少 - 我们最终每个段只有一个对象。这些对象在虚拟机空间中的位置完全取决于操作系统的内存管理器。这意味着,碎片主要发生在操作系统级别 - 而不是 CLR。这是堆碎片的一个经常被监督的方面,它不是 .NET 的责任。(但这也是事实,碎片也可能发生在托管方面,就像那篇文章中很好地展示的那样。)
常见的解决方案已经命名:重用你的大对象。到目前为止,我没有遇到任何通过适当的设计无法做到的情况。但是,有时它可能会很棘手,因此可能很昂贵。
我们在多个线程中处理图像。由于图像足够大,这也会由于内存碎片而导致 OutOfMemory 异常。我们试图通过使用不安全的内存和为每个线程预分配堆来解决这个问题。不幸的是,这并没有完全帮助,因为我们依赖于几个库:我们能够在我们的代码中解决问题,但不是第 3 方。
最终,我们用进程替换了线程,让操作系统来完成繁重的工作。操作系统早就为内存碎片构建了解决方案,因此忽略它是不明智的。
我在另一个答案中看到 LOH 可以缩小尺寸:
” ...现在,话虽如此,如果 LOH 末端的区域完全没有活动物体,则 LOH 的大小可能会缩小,所以唯一的问题是如果您将物体长时间留在其中(例如应用)。 ... ”
除此之外,您可以让您的程序在 32 位系统上以高达 3GB 的扩展内存运行,在 64 位系统上以高达 4 GB 的内存运行。只需在链接器或此构建后事件中添加标志 /LARGEADDRESSAWARE:
调用“$(DevEnvDir)..\tools\vsvars32.bat”editbin /LARGEADDRESSAWARE“$(TargetPath)”
最后,如果您计划使用大量大对象长时间运行程序,您将不得不优化内存使用,您甚至可能不得不重用分配的对象以避免垃圾收集器,这在概念上与使用类似实时系统。