【问题标题】:How To Diagnose A System.OutOfMemoryException?如何诊断 System.OutOfMemoryException?
【发布时间】:2011-04-11 15:48:27
【问题描述】:

我上周五下班前运行了一些代码,我周一在这里,它因 OutOfMemoryException 而停止。我估计整个过程需要进行数百亿次的计算,所以这不是一个小任务。

我什至不知道如何开始解决这个问题。
有什么指点吗?

【问题讨论】:

  • 你有什么推荐的吗?
  • 您对生成的计算结果做了什么?
  • @Hans:不是真的——他在询问如何开始解决这个问题的技巧。内存分析器是一种可能的解决方案,但不是开始调试内存泄漏所需的唯一解决方案。
  • 这段代码是你写的吗?如果没有,请与供应商联系。

标签: c# out-of-memory


【解决方案1】:

最简单、最直接的解决方案是这样的:

  1. 使用procdump(包含在Sysinternals Suite 中)在进程达到某个不合理大小时对其进行完整的内存转储。
  2. 将转储加载到 WinDbg 并使用 .loadby sos clr 加载 SOS 调试扩展
  3. 使用以下命令:!dumpheap -stat 以查看哪种类型的对象占用的内存最多。
  4. 使用!dumpheap -type <MY_TYPE> 转储对象列表(泄漏类型)
  5. 选择几个实例,然后发出命令:!gcroot <OBJ_ADDRESS>。输出应该告诉您哪个对象仍然持有对它的引用,以及为什么该对象没有被释放。

如果您怀疑泄漏源来自某些本机代码,您可以通过发出以下命令来验证这一点:!eeheap -gc。输出将告诉您托管堆占用了多少内存。如果您的进程的私有工作集大小远大于托管堆的大小,那么您可能手上有一个本机泄漏(或者,也许由于某种原因,您产生了 很多线程,因此由于线程的堆栈而空间不足[您可以通过发出以下命令来检查进程中产生了多少线程:~*,或:!threads 仅显示托管线程]) .

【讨论】:

    【解决方案2】:

    如果您重复分配非常大的数组,您可能会遇到Large Object Heap Fragmentation。如果是这种情况,您可能需要考虑是否可以重用工作数组以防止重新分配和后续碎片。但是,我绝对建议您运行内存分析器,因为您更有可能在您认为的过期日期之后在内存中徘徊。

    【讨论】:

    • 我使用的是静态数组,因此它所在的类的每个实例都可以添加到其中。我的程序中有大约 1200 万个此类的实例。如果这个静态数组是出现这个错误的原因,我怎样才能更好地管理这个数组正在使用的内存?
    • 数组是静态的,就像类的静态成员一样?如果是这样的话,我不希望这会导致 LOH 碎片,因为它只是一个数组。
    【解决方案3】:

    我知道您将计算结果存储在一个容器中,如列表或数组。尝试将结果写入文件而不是将它们存储在内存中。

    【讨论】:

    • 它在一个静态数组中,因此它所在的类的每个实例都可以添加到其中。我的程序中有大约 1200 万个此类的实例。如果这个静态数组是出现这个错误的原因,我怎样才能更好地管理这个数组正在使用的内存?
    【解决方案4】:

    Eric Lippert 的这篇文章可能会有所帮助:“Out Of Memory” Does Not Refer to Physical Memory

    【讨论】:

      【解决方案5】:

      您可以尝试几种方法。

      首先,使用 Windows 内置的性能监视器进行粗略的测试。为 Process -> Private bytes used 添加一个计数器,并相应地调整缩放比例,以便您可以快速查看内存使用情况。您无需等待一夜之间,而是可以更快地发现泄漏。您可以在 .NET 内存中找到另一个您可以尝试的计数器,但由于我的性能监视器目前不允许我添加 .NET 计数器(ARGH!),因此我无法准确告诉您子选择是什么。

      您是否通过计算函数中的非托管代码分配内存?如果是这样,您是否正确处理内存?您编写的任何以这种方式分配内存的代码都应该实现 IDisposable,并且您还应该在这些类上调用 Dispose() 以确保它们被清理。

      你是在递归调用你的函数吗?您可能每次都通过函数分配大量内存,虽然您可能不会破坏堆栈,但您会用完堆。

      有什么记忆可以让你尽快摆脱吗?即使您没有使用非托管代码,您仍然可以标记您的托管代码,以便垃圾收集器尽快将其删除,然后它就不会在 Gen2 堆中结束。实现 IDisposable,然后在您不再需要闲逛的类上调用 Dispose()。

      你在使用线程吗?您可能正在线程中分配内存,如果您没有通过调用 EndInvoke 正确处理错误,您将以这种方式孤立内存。

      如果一切都失败了,请尝试使用 RedGate 的 ANTS5 等内存分析器。它在我的代码中发现了另一个错误,我在其中注册了一个事件处理程序,但没有取消注册它。结果,我的代码挂在了与此事件相关的所有零碎上,不幸的是,这也挂在了一些内存上。 :)

      希望这会有所帮助。我最近经历了所有这些,这些是我现在能想到的最有用的提示。

      【讨论】:

        【解决方案6】:

        您需要确定它是什么原因造成的,它来自哪里,然后您可以考虑重写该部分以减少内存使用,可能通过将其分成几部分然后释放一些资源。

        尝试在某处引入带有时间戳的日志(执行第 1 步,执行第 2 步)以帮助确定失败的位置,然后如果不明显,您可以询问有关减少内存依赖性的更具体的问题。

        【讨论】:

          【解决方案7】:

          好吧,鉴于您提供的内容,或者缺少内容,我只能在这里提供一些一般性的想法:首先,显而易见的答案是查看异常本身包含的信息,这应该给出你知道你的代码分配在哪里失败了。

          其次,您使用内存分析来更好地了解您的应用程序中发生了什么 - 我是 dotTrace 的用户,但可能有免费的替代方案可用。

          除了这个一般建议之外,您可能还想在您的问题中包含更多信息。你分配什么类型的对象,你什么时候分配,你是否使用原生资源等等。

          【讨论】:

            【解决方案8】:

            啊……这是您真正不想在您的应用程序中遇到的例外情况之一。我们在项目中也确实遇到了这个异常,我们使用 .net 内存分析器来检查内存使用和泄漏。在某种程度上,我们可以减少出现内存不足异常的频率。下面给出了探查器的链接-

            http://memprofiler.com/

            【讨论】:

            • 我发现 SciTech profiler 比 ANTS 更容易使用。值得一试。
            • @spender - 谢谢。我们也可以试用这个新的分析器。您能否提供此 SciTech 分析器的链接?
            • @spender - 我们也用过。检查我的答案中的链接;)
            • @Sachin:交叉线!我只是同意您的建议(和链接)很好。我 100% 知道“SciTech 分析器”和您答案中链接指向的分析器是一回事。 ;)
            • @spender - 哈哈.. 好吧,我以为他们是不同的。谢谢。是的,我们使用了这个,它真的很容易使用,就像你说的那样......
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-08-25
            • 2010-11-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多