【问题标题】:C# .NET Garbage Collection not functioning?C# .NET 垃圾收集不起作用?
【发布时间】:2011-09-16 12:23:06
【问题描述】:

我正在 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() 时,内存使用问题似乎已经消失。有谁知道什么可能导致这种高内存使用?是否可能与在Debug模式下运行有关?

【问题讨论】:

  • Release 模式下是否会重复同样的问题?
  • 单个字节数组有多大?它们可能已被放入大对象堆中,它不像其他所有东西那样经常被收集。系统是否存在内存压力?
  • 您用于“内存使用”的值是多少?虚拟大小?私有字节?
  • @agent-j LOH 对象被收集,但 GC 不会对 LOH 中的可用空间进行碎片整理,因此即使内存中没有太多对象,应用程序也会遇到内存不足的问题。
  • 在我们下结论之前,让我们确保它们确实是大数组。除非您想深入了解son of strike 并查看它们在哪个堆中。@AlexAtNet,关于碎片的好点。我从未真正见过它,但我不得不寻找一两次。

标签: c# .net garbage-collection


【解决方案1】:

查找泄漏失败后

加倍努力 =)

托管语言中的内存泄漏可能很难追踪。我对Redgate ANTS Memory Profiler 有很好的体验。它不是免费的,但他们为您提供 14 天的全功能试用。它有一个漂亮的用户界面,可以向您显示内存分配的位置以及这些对象为何保存在内存中。

正如 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 即使在调用 Dispose() 之后也没有释放其内部 byte[]。将流包装在一个虚拟流中并将其归零可以解决问题。

另外,很明显,请确保您的对象中的Dispose() 实现了IDisposable。周围可能有原生资源。同样,一个好的分析器会捕捉到这一点。

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

【讨论】:

  • 谢谢,非常有帮助的帖子。我不知道阻止收集对象的事件处理程序,我一定会调查的。我稍后会发布结果。
  • 我卸载了一些仍在使用的事件处理程序,就像你建议的那样。这大大降低了内存使用量。感谢您的帮助。
【解决方案2】:

首先,GC 运行良好,而且运行良好。没有您刚刚发现的错误。

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

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

您的分析器应该告诉您是什么占用了这么多内存。开始尽可能多地消除最大的罪魁祸首。

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

【讨论】:

  • - 我使用了许多线程,但不是我所说的“太多”。我认为我在任何时候都不会运行超过 5 个线程。 - 我知道 GC 基本上会做它想做的任何事情,而 GC.Collect() 只是对 GC 的一个建议,它可能需要清理一些东西。然而,这使得 Collect() 调用会解决内存问题变得更加奇怪。 - 我的引用肯定超出了范围,每当我不再需要它们时,我都会明确地将字节数组引用设置为 null。 - 使用最多内存的是包含 32 位 RGBA 纹理数据的字节数组。
  • “我的引用肯定超出了范围,每当我不再需要它们时,我都会明确地将字节数组引用设置为 null” - 除非当然有其他对 byte[] 的引用。也就是说,这听起来像是 LOH 碎片。分析器肯定会告诉你。
【解决方案3】:

在 .NET 中分析内存问题并非易事,您绝对应该阅读几篇好文章并尝试不同的工具来获得结果。经过调查,我最终得到了以下文章:http://www.alexatnet.com/content/net-memory-management-and-garbage-collector您也可以尝试阅读Jeffrey Richter的一些文章,例如:http://msdn.microsoft.com/en-us/magazine/bb985010.aspx

根据我的经验,内存不足问题有两个最常见的原因:

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

【讨论】:

  • 我对 STA 线程一无所知。也就是说,在WPF中如果UI线程真的很忙,GC就不能收集?你有一篇我可以读到的文章吗?
  • 但是如果手动调用GC.Collect 可以解决问题,那么显然不是 GC 无法 收集对象或者它认为它们是有根的,更多它认为没有必要收集它们。
  • @LukeH:没错。我真的很想知道为什么它认为没有必要收集它们,如果系统抛出 OutOfMemoryExceptions...
【解决方案4】:

编辑:添加了Large Object Heap fragmentation的链接。

编辑:既然看起来是分配和丢弃纹理的问题,你可以使用Texture2D.SetData来重用大字节[]s吗?

首先,您需要确定泄漏的是托管内存还是非托管内存。

  1. 使用 perfmon 查看您的进程 '.net memory# Bytes in all Heaps' 和 Process\Private Bytes 会发生什么。比较数字和记忆上升。如果私有字节的增长超过堆内存的增长,那么它就是非托管内存增长。

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

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

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

  5. 最后一个问题是固定内存,但我对此没有任何建议,因为我从来没有搞砸过。

【讨论】:

  • 添加了建议的解决方案:重用 Texture2D 对象。
  • 已经在使用 SetData 和 FromStream 在其他一些场合。没想过重用 Texture2D 对象,谢谢。
【解决方案5】:

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

另外,我通常对资源使用 using 子句,例如 Sql Connection:

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

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

【讨论】:

  • 一般来说是个好建议,但IDisposable 与托管内存的释放无关。
  • 是的,我完全同意你的看法。但我不会假设他们只使用托管资源。事实上,当我阅读 XNA 游戏时,我立即想知道他们可能使用了哪些非托管资源。
  • 关闭 IO 流是我经常做的事情 - 但无论如何感谢指针。
【解决方案6】:

GC 不考虑非托管堆。如果您在 C# 中创建大量对象,而这些对象仅仅是对更大的非托管内存的包装,那么您的内存正在被吞噬,但 GC 无法基于此做出合理的决定,因为它只看到托管堆。

您最终会遇到 GC 收集器不认为您内存不足的情况,因为您的第 1 代堆上的大部分内容都是 8 字节引用,实际上它们就像海上的冰山。大部分内存都在下面!

您可以使用这些 GC 调用:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-20
    • 2011-06-16
    • 1970-01-01
    相关资源
    最近更新 更多