是啥导致 .NET 中的内存碎片
Posted
技术标签:
【中文标题】是啥导致 .NET 中的内存碎片【英文标题】:What causes memory fragmentation in .NET是什么导致 .NET 中的内存碎片 【发布时间】:2011-07-11 13:59:54 【问题描述】:我正在使用 Red Gates ANTS 内存分析器来调试内存泄漏。它一直在警告我:
内存碎片可能导致 .NET 保留过多的可用内存。
或
内存碎片正在影响可以分配的最大对象的大小
因为我有强迫症,这个问题必须解决。
有哪些标准编码做法有助于避免内存碎片。 你能通过一些.NET 方法对其进行碎片整理吗?它甚至会有所帮助吗?
【问题讨论】:
这将有助于了解这是什么类型的应用程序。如果您将内存固定(或使用在后台固定 I/O 缓冲区的 I/O 函数)、从本机分配器(例如 COM 任务分配器)进行分配或创建大量大型对象,则会发生内存碎片,因为LOH 没有被压缩。 .NET 垃圾收集器已经压缩了代际动态分配,这具有对可用空间进行碎片整理的副作用。如果这没有发生,那是因为某些东西阻止了对象被移动。 因为我有强迫症,这个问题必须解决。 + 1 仅用于此评论 - 我其实很喜欢这个问题 卸载那些讨厌你但对诊断问题没有帮助的工具。内存碎片是生活中的一个事实,没有什么可以阻止它,这不会是非常不切实际的。低碎片堆分配器已经是 Vista 及更高版本的默认设置。无论如何,如果你分配了超过一半的可用地址空间,这只是一个问题,猪不会飞。 @Hans - 低碎片堆与专门托管的代码无关 - 托管堆根本不使用本机堆。不过,您的其余评论完全正确。 @Stewart - 大多数碎片都是由非托管代码引起的。有很多,即使在纯托管程序中也是如此。 GC 几乎不会产生碎片,因为它会压缩堆,这是非托管代码无法做到的。 【参考方案1】:你知道,我有点怀疑这里的内存分析器。 .NET 中的内存管理系统实际上会尝试通过在内存中移动来为您整理堆碎片(这就是为什么您需要固定内存以使其与外部 DLL 共享)。
占用较长时间的大内存分配容易产生更多碎片。虽然小的临时(短)内存请求不太可能导致 .NET 中的碎片。
这也是值得思考的事情。在 .NET 的当前 GC 中,分配的内存在时间上很接近,通常在空间上间隔很近。这与碎片化相反。即您应该按照您打算访问它的方式分配内存。
它只是托管代码还是包含 P/Invoke、非托管内存 (Marshal.AllocHGlobal) 或 GCHandle.Alloc(obj, GCHandleType.Pinned) 之类的东西?
【讨论】:
GC 不会压缩大于 85KB 的对象所在的大型对象堆。一旦 LOH 碎片化,就无法对其进行碎片整理。 从 .NET 4.5.1m 开始,有一种手动压缩 LOH 的方法,但我强烈建议不要这样做,因为它会对您的应用程序造成巨大的性能影响。 blogs.msdn.microsoft.com/mariohewardt/2013/06/26/…(再次,我建议不要这样做)【参考方案2】:GC 堆以不同的方式处理大对象分配。它不会压缩它们,而只是组合相邻的空闲块(如传统的非托管内存存储)。
更多信息在这里:http://msdn.microsoft.com/en-us/magazine/cc534993.aspx
因此,对于非常大的对象,最好的策略是分配一次,然后保留并重用它们。
【讨论】:
出于好奇,我想知道为什么 LOH 对象大小没有四舍五入到 4096 的下一个倍数?看起来这将有助于在某些操作系统上下文中进行压缩(只需移动虚拟页面指针而不是复制内存),并且还会大大减少碎片。由于 LOH 对象通常最小为 85K,因此四舍五入到 4K 块的开销将是 5% 或更少。 @supercat,该评论值得在自己的问题中提出。如果你现在知道答案,请告诉我。 @mbadawi23:至少在 .NET 2.0 中,LOH 将用于一些不是很大的对象。例如,我认为任何超过 1,000 个元素的double[]
都会被强制进入 LOH。将double[1024]
分配为三个 4096 字节的块将是相当浪费的。当然,我真正的怀疑是分配double[1024]
并不是一个好主意。【参考方案3】:
.NET Framework 4.5.1 能够在垃圾回收期间显式压缩大对象堆 (LOH)。
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
在GCSettings.LargeObjectHeapCompactionMode查看更多信息
【讨论】:
我强烈建议不要使用它,因为它会对您的应用程序造成巨大的性能影响,原因有两个:1. 耗时 2. 它清除了 GC 的任何分配模式算法在您的应用程序的整个生命周期内收集。当您的应用程序运行时,GC 实际上会通过了解您的应用程序如何分配内存来调整自己。因此,应用程序运行的时间越长,它就会变得越高效(在一定程度上)。当您执行 GC.Collect() (或任何重载)时,它会清除 GC 学习到的所有数据 - 所以它必须重新开始。 @Dave Black,你在哪里找到这样的信息? MSDN 不包含有关 LOH 压缩对分配模式算法的影响的信息。以上是关于是啥导致 .NET 中的内存碎片的主要内容,如果未能解决你的问题,请参考以下文章
如果由于CMS gc算法中的关键内存碎片而无法分配内存会发生什么