我正在Visual Studio 2010中开发一个相对较大的解决方案。它有各种项目,其中一个是XNA游戏项目,另一个是ASP.NET MVC 2项目。
对于这两个项目,我面临同样的问题:在调试模式下启动它们后,内存使用率不断上升。它们分别以40和100MB的内存使用量开始,但两者都相对较快地升至1.5GB(分别为10分钟和30分钟)。之后它有时会回落到接近初始使用状态,有时它会抛出OutOfMemoryExceptions
。
当然这表明存在严重的内存泄漏,所以这是我最初试图发现问题的地方。在不成功地搜索泄漏之后,我尝试定期调用GC.Collect()
(大约每10秒一次)。在引入这个“hack”之后,内存使用率分别保持在45和120MB 24小时(直到我停止测试)。
.NET的垃圾收集应该是“非常好”,但我不禁怀疑它只是不起作用。我使用CLR Profiler试图解决问题,它表明XNA项目似乎已经保存了很多我确实使用的字节数组,但是引用应该已经被删除,因此被垃圾收集收集器。
同样,当我定期调用GC.Collect()
时,内存使用问题似乎已经消失。有谁知道什么可能导致这种高内存使用?它可能与在调试模式下运行有关吗?
答案 0 :(得分:13)
在不成功地搜索泄漏之后
努力尝试=)
托管语言中的内存泄漏可能很难追踪。我对Redgate ANTS Memory Profiler有过很好的体会。它不是免费的,但它们会为您提供为期14天的全功能试用版。它有一个很好的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中遇到了碎片。如果您经常分配大型对象,则可能会导致问题。这些对象的总大小可能比分配给运行时的总内存小得多,但由于碎片,会为应用程序分配大量未使用的内存。
我上面链接的探查器会告诉您这是否有问题。如果是,您可能会将其追踪到某处的物体泄漏。我刚刚修复了我的应用中显示相同行为的问题,这是因为MemoryStream
即使在调用byte[]
之后也没有释放其内部Dispose()
。将流包装在虚拟流中并将其归零可以解决问题。
另外,说明显而易见的,请确保实现Dispose()
的对象IDisposable
。可能存在本地资源。再一次,一个好的剖析器会抓住这个。
我的建议;它不是GC,问题出在你的应用程序中。使用分析器,让您的应用程序处于高内存消耗状态,获取内存快照并开始分析。
答案 1 :(得分:5)
首先,GC工作正常,效果很好。你刚刚发现它没有错误。
现在我们已经解决了这个问题,一些想法:
GC.Collect()
。你的探查器应该告诉你什么在使用这么多内存。尽可能多地开始削减最大的罪魁祸首。
此外,每隔X秒调用GC.Collect()是一个坏主意,不太可能解决您的实际问题。
答案 2 :(得分:4)
答案 3 :(得分:3)
修改:添加了Large Object Heap fragmentation的链接。
编辑:由于分配和丢弃纹理看起来有问题,你可以使用Texture2D.SetData重用大字节[] s吗?
首先,您需要弄清楚它是否是泄漏的托管或非托管内存。
使用perfmon查看您的进程'.net内存中的字符数#所有堆中的字节数'和Process\Private Bytes
。比较数字和内存上升。如果Private字节的增长超过堆内存的增长,那么它就是非托管内存增长。
非托管内存增长会指向未被处置的对象(但最终会在终结器执行时收集)。
如果管理内存增长,那么我们需要查看哪一代/ LOH(每一代堆字节都有性能计数器)。
如果是大对象堆字节,则需要重新考虑使用和丢弃大字节数组。也许字节数组可以重复使用而不是丢弃。另外,考虑分配2的幂的大字节数组。这样,在处理时,你将在大对象堆中留下一个大的“洞”,可以被另一个相同大小的对象填充。
< / LI>最后一个问题是寄托记忆,但我对此没有任何建议,因为我从来没有搞过它。
答案 4 :(得分:1)
我还要补充一点,如果您正在进行任何文件访问,请确保您正在关闭和/或处置任何读者或作者。打开任何文件并关闭它之间应该匹配1-1。
另外,我通常使用using子句来处理资源,比如Sql Connection:
using (var connection = new SqlConnection())
{
// Do sql connection work in here.
}
您是否在任何对象上实现IDisposable并且可能执行导致任何问题的自定义操作?我会仔细检查你所有的IDisposable代码。
答案 5 :(得分:0)
GC没有考虑非托管堆。如果你创建了许多只是C#中包装器的对象而不是更大的非托管内存,那么你的内存正在被吞噬,但GC不能基于此做出合理的决定,因为它只能看到托管堆。
你最终会遇到GC收集器并不认为你缺少内存的情况,因为你的gen 1堆上的大多数东西都是8字节的引用,实际上它们就像海上的冰山一样。大部分记忆都在下面!
您可以使用这些GC调用:
System::GC::AddMemoryPressure(sizeOfField);
System::GC::RemoveMemoryPressure(sizeOfField);
这些方法允许垃圾收集器查看非托管内存(如果您提供正确的数字)