【问题标题】:My (huge) application throws an OutOfMemoryException, now what?我的(巨大的)应用程序抛出了 OutOfMemoryException,现在怎么办?
【发布时间】:2010-12-13 07:26:47
【问题描述】:

这是迄今为止我构建的最复杂的软件,现在它似乎在某些时候内存不足。我还没有进行广泛的测试,因为我有点迷茫我应该如何解决手头的问题。

HandleCount: 277
NonpagedSystemMemorySize: 48136
PagedMemorySize: 1898590208
PagedSystemMemorySize: 189036
PeakPagedMemorySize: 1938321408
VirtualMemorySize: 2016473088
PeakVirtualMemory: 2053062656
WorkingSet: 177774592
PeakWorkingSet: 883834880
PrivateMemorySize: 1898590208
PriviligedProcessorTime: 00:00:15.8593750
UserProcessorTime: 00:00:01.6562500
TotalProcessorTime: 00:00:17.5156250
GDI Objects: 30
User Objects: 27

我有一个自动全局异常捕获器,它会在异常时收集上述信息(使用 System.Diagnostics.Process)——连同异常信息、日志和屏幕截图——并将所有内容通过电子邮件发送给我。

这一直运行良好,因为我已经能够根据电子邮件信息插入错误。这是,直到现在。该软件有数万行,使用托管和非托管资源。

我可以开始逐行查看代码,但我觉得这可能不是尝试推断内存堆积问题的最佳方法。

由于我以前从未做过此类分析,您建议如何处理此类问题?

【问题讨论】:

    标签: c# .net memory profiling


    【解决方案1】:

    我们为此提供了一个工具。

    http://msdn.microsoft.com/en-us/library/ms979205.aspx

    CLR Profiler 使您能够查看 进程的托管堆和 调查对方的行为 垃圾收集器。使用各种 工具中的视图,您可以获得 有关的有用信息 执行、分配和内存 消耗您的应用程序。

    使用 CLR Profiler,您可以 识别分配过多的代码 内存,导致太多垃圾 收藏,并保存在内存中 太久了。

    【讨论】:

      【解决方案2】:

      有几个选项。专用内存分析器(例如 RedGate 的 ANTS Memory Profiler)对于解决此类问题非常有用。

      如果您不想花钱购买专用工具,也可以使用 WinDbg(Debugging tools for Windows 的一部分,可从 Microsoft 免费下载)。它可以显示托管堆、各种 AppDomain 堆等的堆使用情况。

      查看this blog 以获取有关使用 WinDbg 的提示。

      请记住,排除内存不足问题可能很困难,因为您通常看不到实际问题,而只是症状。因此,与调用堆栈可以很好地指示问题根源的崩溃不同,带有 OOM 的进程的调用堆栈可能显示的很少。

      根据我的经验,您必须查看内存的使用位置。它可能在托管堆上,在这种情况下,您必须找出是否有东西在实例上的持有时间超过了必要的时间。但是,它也可能与加载大量程序集(通常是动态生成的程序集)有关。

      【讨论】:

        【解决方案3】:

        查看这篇关于在 .NET 应用程序中检测内存泄漏的 MSDN 文章。

        也许你有一些问题,内存被分配并且从未被收集。

        【讨论】:

          【解决方案4】:

          将调试器附加到它并重现错误。异常时的调用堆栈应该告诉您错误在哪里。

          要么你有内存泄漏,要么你没有处理你的对象,或者你需要更好的硬件:)

          【讨论】:

          • 恕我直言,在这种情况下,使用调试器捕获异常是没有用的,损坏(很可能是内存泄漏)已经在其他地方完成了。
          • 只是抛出了几个选项。不会伤害到看。
          • tslib 有一点,有时候内存不够的时候可以缩小范围。
          • ++ 假设您可以在抛出异常时进入调试器,那么失败的分配很可能是导致内存泄漏的分配。
          【解决方案5】:

          我有完全相同的应用程序。 :) 我们的应用程序使用最多 10GB 的 RAM。这显然很糟糕。经过一些优化后,我设法将内存使用量减少了大约 50 倍,所以现在相同的数据集占用了 200MB。魔法?不。:) 我做了什么:

          1. 一些数据在内存中存储了多次(几个副本)。我为每组数据制作了一份副本。
          2. 一些数据存储为string,但更有效的方式是int,因为这些字符串只包含数字。
          3. 主要的数据存储类是Dictionary<uint,uint>We wrote our own dictionary 不存储任何哈希 - 结果内存使用量在 64 位系统上减少了 3 倍,在 32 位系统上减少了 2 倍。

          所以我的问题是:您用来存储数据的主要类/对象是什么?您存储什么样的数据?

          【讨论】:

            【解决方案6】:

            您的 PeakWorkingSet 表示 32 位 CLR 开始爆炸时的公共编号。

            尽管人们告诉你什么,尽管自动内存管理具有巨大的讽刺意味,但你必须意识到这一点,并确保你永远不会接近此类/32 位系统的限制。许多人没有意识到这一点,我通常喜欢接受他们的 C# bloat downvotes ,但是当您在单个桌面上运行一些这样的应用程序时,您可以预期会造成一些破坏。看看VS关闭的托管部分,就像一列火车在PC上运行。

            有一个免费的 .NET 的 MemProfiler,使用它并寻找悬根。最终,特别是当您开始处理中等大小的数据时,您将不得不使用流式设计而不是依赖它来运行具有更多 RAM 的 x64。

            如今,拥有 c880MB 数据集的数据集大小可悲。事实!

            [C# 3.0 羊的片段]

            【讨论】:

              【解决方案7】:

              也许您应该首先检查使用非托管资源的地方。问题可能是你没有释放它们,或者你没有正确地释放它们。

              【讨论】:

                【解决方案8】:

                已经提出了很多有用的解决方案,并且 MSDN 文章非常详尽。结合上述建议,我还将执行以下操作;

                将异常发生的时间与您的日志文件相关联,以查看 OOM 异常发生时发生的情况。如果您在信息或调试级别几乎没有日志记录,我建议您添加一些日志记录,以便您了解此错误的上下文。

                内存使用量是在异常发生之前的很长一段时间内逐渐增加(例如,无限期运行的服务器进程)还是在异常发生之前它会迅速大幅增加?是运行很多线程还是只有一个?

                如果第一个为真并且异常长时间没有发生,则意味着资源正在泄漏,如上所述。如果后者是真的,那么许多事情可能会导致原因,例如每次迭代分配大量内存的循环,从服务接收大量结果等。

                无论哪种方式,日志文件都应为您提供有关从何处开始的足够信息。从那里我将确保我可以通过在界面中发出一组特定的命令或使用一组一致的输入来重新创建错误。之后,根据代码的状态,我将尝试(使用日志文件信息)创建一些针对假定的问题根源的集成测试。这应该可以让您更快地重新创建错误条件,并使其更容易找到,因为您专注的代码会更小。

                我倾向于做的其他事情是使用小型分析类围绕内存敏感代码。这可以将内存使用情况记录到日志文件中,并让您立即了解日志中的问题。该类可以进行优化,因此它不会编译到发布版本中或具有很小的性能开销(如果您需要更多信息,请联系我)。这种方法在分配大量线程时效果不佳

                您提到了非托管资源,我假设您/您的团队编写的所有代码都是托管的?如果不是,并且如果可能的话,我将使用类似于上面提到的分析类围绕非托管边界,以排除非托管代码或互操作的泄漏。固定大量非托管指针也会导致堆碎片,但如果您没有非托管代码,这两个点都可以忽略。

                不鼓励在之前的评论中明确调用垃圾收集器。尽管您应该很少这样做,但有时它是有效的(搜索 Rico Mariani 的博客以获取示例)。我明确调用 collect 的一个示例(在提到的博客中有介绍)是从服务返回大量字符串、放入数据集然后绑定到网格时。即使在屏幕关闭后,这段记忆也有一段时间没有被收集起来。一般来说,它不应该被显式调用,因为垃圾收集器维护着它收集(以及其他东西)所依据的指标。显式调用 collect 会使这些指标无效。

                最后,了解应用程序的内存需求通常是件好事。通过记录更多信息、偶尔运行分析器、压力/单元/集成测试来获得此信息。了解某个操作在高层次上的影响,例如基于一组输入,大约 x 将被分配。我通过在日志文件中的战略点注销详细信息来了解这一点。臃肿的日志文件可能难以理解或解释。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2011-03-25
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-04-10
                  • 1970-01-01
                  • 2013-01-31
                  相关资源
                  最近更新 更多