C#.NET垃圾收集无法正常运行?

我正在Visual Studio 2010中开发一个相对较大的解决方案。它有各种项目,其中一个是XNA Game-project,另一个是ASP.NET MVC 2项目。

对于这两个项目,我面临同样的问题:在调试模式下启动它们后,内存使用率不断上升。 它们的内存使用率分别为40和100MB,但两者都相对较快地升至1.5GB(分别为10分钟和30分钟)。 之后它有时会回落到接近初始使用状态,有时它会抛出OutOfMemoryExceptions

当然这表明存在严重的内存泄漏,所以这是我最初试图发现问题的地方。 在不成功地搜索泄漏之后,我尝试定期调用GC.Collect() (大约每10秒一次)。 在引入这个“hack”之后,内存使用量分别保持在45和120MB 24小时(直到我停止测试)。

.NET的垃圾收集应该是“非常好的”,但我不禁怀疑它只是不起作用。 我使用CLR Profiler试图解决问题,它表明XNA项目似乎已经保存了很多我确实使用的字节数组,但是引用应该已经被删除,因此被垃圾收集集电极。

同样,当我定期调用GC.Collect() ,内存使用问题似乎已经消失。 有谁知道什么可能导致这种高内存使用? 是否可能与在调试模式下运行有关?

在不寻常地寻找泄漏之后

努力尝试=)

托管语言中的内存泄漏可能很难追踪。 我对Redgate ANTS Memory Profiler有很好的经验。 它不是免费的,但它们会为您提供为期14天的全function试用版。 它有一个很好的UI,可以显示内存的分配位置以及这些对象保存在内存中的原因。

正如Alex所说,事件处理程序是.NET应用程序中非常常见的内存泄漏源。 考虑一下:

 public static class SomeStaticClass { public event EventHandler SomeEvent; } private class Foo { public Foo() { SomeStaticClass.SomeEvent += MyHandler; } private void MyHandler( object sender, EventArgs ) { /* whatever */ } } 

我使用静态类来使问题在这里尽可能明显。 让我们说,在应用程序的生命周期中,创建了许多Foo对象。 每个Foo订阅静态类的SomeEvent事件。

Foo对象可能在某个时间或某个时间超出范围,但静态类通过事件处理程序委托维护对每个对象的引用。 因此,它们无限期地保持活着。 在这种情况下,事件处理程序只需要“取消挂钩”。

… XNA项目似乎已经保存了很多我确实使用的字节数组…

您可能在LOH中遇到了碎片。 如果您经常分配大型对象,则可能会导致问题。 这些对象的总大小可能比分配给运行时的总内存小得多,但由于碎片,会为应用程序分配大量未使用的内存。

我上面链接的探查器会告诉你这是否有问题。 如果是,您可能会将其跟踪到某处的对象泄漏。 我刚刚在我的应用程序中修复了一个显示相同行为的问题,这是因为即使在调用Dispose()之后, MemoryStream没有释放其内部byte[] 。 将流包装在虚拟流中并将其清零以解决问题。

另外,说明显而易见的,确保Dispose()实现IDisposable的对象。 可能存在本地资源。 再次,一个好的剖析器将抓住这一点。

我的建议; 它不是GC,问题出在你的应用程序中。 使用分析器,让您的应用程序处于高内存消耗状态,获取内存快照并开始分析。

首先,GC工作正常,效果很好。 你刚刚发现它没有错误。

现在我们已经解决了这个问题,一些想法:

  1. 你使用太multithreading吗?
  2. 请记住,GC是非确定性的; 只要它认为需要运行它就会运行(即使你调用GC.Collect()
  3. 您确定所有参考文献都超出了范围吗?
  4. 你首先在内存中加载了什么? 大图像? 大文本文件?

你的探查器应该告诉你什么使用了这么多的内存。 尽可能多地开始削减最大的罪魁祸首。

此外,每隔X秒调用GC.Collect()是一个坏主意,不太可能解决您的实际问题。

分析.NET中的内存问题并不是一项简单的任务,您肯定应该阅读几篇好文章并尝试使用不同的工具来实现结果。 调查结束后,我最终得到了以下文章: http : //www.alexatnet.com/content/net-memory-management-and-garbage-collector您也可以尝试阅读一些Jeffrey Richter的文章,例如: http : http ://msdn.microsoft.com/en-us/magazine/bb985010.aspx

根据我的经验,Out-Of-Memory问题有两个最常见的原因:

  1. 事件处理程序 – 即使没有其他对象引用它,它们也可以保存对象。 理想情况下,您需要取消订阅事件处理程序以自动销毁对象。
  2. 终结器线程被STA模式中的某个其他线程阻止。 例如,当STA线程执行大量工作时,其他线程将停止,并且无法销毁终结队列中的对象。

编辑:添加了大对象堆碎片的链接。

编辑:因为它看起来像分配和丢弃纹理是一个问题,你可以使用Texture2D.SetData重用大字节[] s?

首先,您需要弄清楚它是否是泄漏的托管或非托管内存。

  1. 使用perfmon查看进程中的.net内存#Bytes’和Process\Private Bytes 。 比较数字和内存上升。 如果Private字节的增长超过堆内存的增长,那么它就是非托管内存增长。

  2. 非托管内存增长将指向未被处置的对象(但最终会在其终结器执行时收集)。

  3. 如果是托管内存增长,那么我们需要查看哪一代/ LOH(每一代堆字节都有性能计数器)。

  4. 如果它是大对象堆字节,您将需要重新考虑使用和丢弃大字节数组。 也许字节数组可以重复使用而不是丢弃。 另外,考虑分配大于2的幂的大字节数组。这样,在处理时,您将在大对象堆中留下一个大的“洞”,可以由另一个相同大小的对象填充。

  5. 最后一个问题是寄托记忆,但我对此没有任何建议,因为我从未搞砸过它。

我还要补充一点,如果您正在进行任何文件访问,请确保您正在关闭和/或处置任何读者或作者。 打开任何文件并关闭它之间应该匹配1-1。

另外,我通常使用using子句来获取资源,比如Sql Connection:

 using (var connection = new SqlConnection()) { // Do sql connection work in here. } 

您是否在任何对象上实现IDisposable并且可能执行导致任何问题的自定义操作? 我会仔细检查你所有的IDisposable代码。

GC没有考虑非托管堆。 如果你创建了许多只是C#中包装器的大型非托管内存的对象,那么你的内存正在被吞噬,但GC无法基于此做出合理的决定,因为它只能看到托管堆。

你最终会遇到GC收集器认为你缺少内存的情况,因为你的gen 1堆上的大多数东西都是8字节的引用,实际上它们就像海上的冰山一样。 大部分内存都在下面!

您可以使用这些GC调用:

 System::GC::AddMemoryPressure(sizeOfField); System::GC::RemoveMemoryPressure(sizeOfField); 

这些方法允许垃圾收集器查看非托管内存(如果您提供正确的数字)