大对象堆碎片化:CLR是否有解决方案?

8
如果您的应用程序需要频繁分配/释放大型对象(>85000字节),最终会导致内存碎片化,并且您的应用程序将抛出内存不足异常。
这个问题有解决方案吗?还是CLR内存管理的限制?

@Aliostad: http://msdn.microsoft.com/en-us/magazine/cc534993.aspx - dthorpe
6个回答

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

以下是有关此问题的MSDN杂志文章:http://msdn.microsoft.com/en-us/magazine/cc534993.aspx,但其中没有太多有用的信息。

3
CLR的垃圾回收器中涉及到大对象的问题是,它们被管理在不同的堆中。垃圾回收器使用一种称为“压缩”的机制,这基本上是普通堆中对象的碎片和重连。问题是,“压缩”大对象(复制和重连它们)是一个昂贵的过程,因此GC为它们提供了一个不会被“压缩”的不同堆。还要注意的是,内存分配是连续的。这意味着如果你先分配Object #1,然后再分配Object #2,那么Object #2将始终被放置在Object #1之后。这可能是导致OutOfMemoryExceptions错误的原因。我建议您查看Flyweight、Lazy Initialization和Object Pool等设计模式。如果怀疑有些大对象已经死亡,并且由于控制流程中存在缺陷而未被收集,从而导致它们在准备好进行收集之前达到更高的代数,也可以强制进行GC收集。

大对象不跟踪代,它们只在完整的收集期间被收集。 - Bryce Wagner
1
如果您分配了对象#1,然后是对象#2,那么对象#2将始终放置在对象#1之后。换句话说,如果您首先释放对象#1,并且它的大小大于或等于对象#2,则CLR可能会在先前由对象#1使用的空间中分配它。 - Daniel Earwicker

2
一个程序因为申请了过大的一块内存而崩溃,而不是因为完全用尽了所有虚拟内存地址空间。你可以认为这是LOH碎片化的问题,也可以认为是程序使用了太多虚拟内存的问题。
一旦一个程序超过了可寻址虚拟内存的一半(1GB),就真的需要考虑让它的代码更加智能化,以便它不会占用太多内存。或者将64位操作系统作为前提条件。后者总是更便宜的选择,而且也不会花费你的钱。

完全同意您关于64位操作系统的看法,但我希望所有客户都愿意根据这个理由升级他们的计算机 :) 我们生活在一个艰难的世界。 - palm snow

1
Is there any solution to this problem or is it a limitation of CLR memory management?

除了重新考虑设计,没有其他解决方案。这不是CLR的问题。请注意,对于非托管应用程序,问题是相同的。这是由于应用程序在内存中同时使用太多内存,并且在内存中处于“不利”的段中。如果必须指向某些外部罪犯,我宁愿指向操作系统内存管理器,它(当然)不会压缩其vm地址空间。

CLR在自由列表中管理LOH的自由区域。在大多数情况下,这是防止碎片化的最佳方法。但是,由于对于真正大的对象,每个LOH段中的对象数量会减少-最终我们只有一个对象每个段。而那些对象在vm空间中的位置完全取决于操作系统的内存管理器。这意味着,碎片主要发生在操作系统级别上,而不是CLR。这是堆碎片化经常被忽视的方面,.NET并不为此负责。(但也是真的,碎片化也可能发生在托管端,就像那篇文章中演示的那样。)

常见的解决方案已经被提出:重复使用您的大型对象。到目前为止,我还没有遇到过任何情况,在这种情况下,通过适当的设计无法完成此操作。然而,有时可能会很棘手,因此可能会很昂贵。


0

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

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


-1

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

大数组和LOH碎片化。什么是公认的惯例?

“... 话虽如此,如果其末尾区域完全没有活动对象,则LOH可以缩小大小,因此唯一的问题是如果您将对象留在那里很长时间(例如应用程序的持续时间)。 ...”

除此之外,您可以在32位系统上扩展内存至3GB,在64位系统上扩展内存至4GB。 只需在链接器中添加标志/LARGEADDRESSAWARE或此后生成事件:

call "$(DevEnvDir)..\tools\vsvars32.bat" editbin /LARGEADDRESSAWARE "$(TargetPath)"

最后,如果您计划长时间运行具有大量大型对象的程序,则必须优化内存使用,并且甚至可能必须重复使用已分配的对象以避免垃圾收集器,这类似于实时系统的工作概念。


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