什么导致.NET中的内存碎片化问题?

50

我正在使用Red Gates ANTS内存分析器来调试内存泄漏。 它一直警告我:

内存碎片可能导致.NET保留太多的空闲内存。

或者

内存碎片会影响可以分配的最大对象大小。

因为我有强迫症,必须解决这个问题。

有什么标准编码实践可以帮助避免内存碎片?能否通过某些.NET方法进行碎片整理?这样做是否有帮助?


6
了解这个应用程序是什么类型的将会很有帮助。如果您将内存固定不释放(或使用在后台固定I/O缓冲区的I/O函数),从本机分配器(例如COM任务分配器)分配,或创建许多大对象,则会发生内存碎片化,因为大对象堆(LOH)不会被压缩。.NET垃圾回收器已经压缩了世代动态分配,这具有整理空闲空间的副作用。如果这样的操作没有发生,那么就是因为某些因素阻止了对象移动。 - Ben Voigt
33
因为我有强迫症,这个问题必须得到解决。对于这个评论单独给一个赞——尽管我很喜欢这个问题。 - BrokenGlass
9
卸载那些指责你但不提供诊断问题帮助的工具。内存碎片化是生活中的一个事实,除非采取非常不切实际的措施,否则无法防止它。低碎片堆分配器已经成为Vista及以上版本的默认设置。只有在你分配了可用地址空间的一半以上时才会成为问题,这几乎是不可能的事情。 - Hans Passant
2
@Hans - 尽管如此,低碎片堆对于纯托管代码并不相关 - 托管堆根本不使用本地堆。 但是,您评论的其余部分完全正确。 - Stewart
@Stewart - 大多数的碎片化都是由非托管代码引起的。即使在一个纯托管程序中,也有很多非托管代码存在。垃圾回收器引起的碎片化很少,因为它可以压缩堆,而非托管代码无法做到这一点。 - Hans Passant
@Hans Passant:不幸的是,大于85K的分配会进入“大对象堆”,微软在其无限智慧的决定中认为这些对象不能被压缩。我知道出于性能原因最好避免压缩大对象堆,除非绝对必要。因为不仅需要移动大对象,还需要处理它们的顺序问题,而这是较小堆不必担心的。尽管如此,即使是一个缓慢的“最后一招”压缩也比崩溃好。 - supercat
3个回答

12

你知道,我对这里的内存分析工具有些怀疑。.NET中的内存管理系统实际上通过移动内存来尝试为您整理堆(这就是为什么您需要固定内存才能与外部DLL共享它的原因)。

长时间占用大量内存的情况容易导致更多的内存碎片。而在.NET中,小的暂时性(短期)内存请求不太可能引起内存碎片。

这里还有一些值得思考的问题。在当前的.NET GC中,接近时间分配的内存通常会在空间上紧密排列,这与内存碎片相反。也就是说,您应该按照访问方式分配内存。

它是仅受管理代码控制还是包含诸如P / Invoke、非托管内存(Marshal.AllocHGlobal)或类似GCHandle.Alloc(obj,GCHandleType.Pinned)的内容?


7
GC不会压缩大对象堆,也就是那些大小超过85KB的对象所在的地方。一旦大对象堆碎片化了,就无法进行碎片整理。 - Tim Robinson
从.NET 4.5.1开始,虽然有一种手动压缩LOH的方法,但我强烈建议不要这样做,因为它会对您的应用程序造成巨大的性能损失。https://blogs.msdn.microsoft.com/mariohewardt/2013/06/26/no-more-memory-fragmentation-on-the-net-large-object-heap/(再次声明,我不建议这样做) - Dave Black

10

GC堆对大对象分配进行了不同的处理,它不会将它们压缩,而是像传统的非托管内存存储一样,只是合并相邻的空闲块。

更多信息请参见: http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

因此,对于非常大的对象,最好的策略是分配它们一次,然后保留它们并重复使用。


2
出于好奇,我想知道为什么 LOH 对象的大小没有舍入到 4096 的下一个倍数?这似乎会在某些 OS 上下文中促进压缩(仅移动虚拟页指针而不是复制内存),并且还可以大大减少碎片化。由于 LOH 对象通常至少为 85K,因此将其舍入为 4K 块的开销将为 5% 或更少。 - supercat
@supercat,这个评论值得成为自己的问题。如果你现在知道答案,请让我知道。 - mbadawi23
@mbadawi23:至少在.NET 2.0中,LOH会用于一些不是很大的对象。例如,我认为任何超过1,000个元素的double[]都会被强制进入LOH。将double[1024]分配为三个4096字节的块会非常浪费。当然,我的真正怀疑是分配一个double[1024]本来就不是一个好主意。 - supercat

9

.NET Framework 4.5.1可以在垃圾回收期间明确地压缩大对象堆(LOH)。

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();

请参阅GCSettings.LargeObjectHeapCompactionMode获取更多信息。


我强烈建议不要这样做,因为它会对您的应用程序产生巨大的性能影响,原因如下:
  1. 它非常耗时。
  2. 它会清除GC在应用程序生命周期内收集的任何分配模式算法。当您的应用程序运行时,GC实际上通过学习您的应用程序分配内存的方式来调整自身。因此,随着您的应用程序运行时间越长,它变得更加高效(在一定程度上)。当您执行GC.Collect()(或其任何重载)时,它会清除GC所学习的所有数据,因此必须重新开始。
- Dave Black
1
@Dave Black,你在哪里找到这样的信息?MSDN上没有关于LOH压缩对分配模式算法影响的信息。 - 23W

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