我的(巨大的)应用程序抛出OutOfMemoryException,现在是什么?

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

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 

我有一个自动化的全局exception捕获器,在exception时收集上述信息(使用System.Diagnostics.Process) – 以及exception信息,日志和屏幕截图 – 并通过电子邮件发送给我所有内容。

这已经很好地工作了,因为我已经能够根据电子邮件信息插入错误。 到目前为止,这是。 该软件有数万行,并使用托管和非托管资源。

我可以逐行开始编写代码,但有些我觉得这可能不是尝试推断内存构建问题的最佳方法。

由于我以前从未做过这种分析,你会如何建议解决这类问题?

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

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

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

使用CLR Profiler,您可以识别分配太多内存,导致过多垃圾收集以及持久存储的代码。

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

如果您不想在专用工具上花钱,您还可以使用WinDbg( Windows的调试工具的一部分,可以从Microsoft免费下载)。 它可以显示托管堆的堆使用情况,各种AppDomain堆等等。

看看这篇博客 ,了解使用WinDbg的提示。

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

根据我的经验,你必须看看内存的使用位置。 它可能位于托管堆上,在这种情况下,您必须查明某些内容是否持续时间超过必要的实例。 但是,它也可能与加载大量组件(通常是动态生成的组件)有关。

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

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

将调试器附加到它并重现错误。 exception时的调用堆栈应该告诉您错误的位置。

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

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

  1. 一些数据存储在存储器中几次(几个副本)。 我为每一组数据制作了一份副本。
  2. 有些数据存储为string ,但更有效的方法是int因为这些字符串只包含数字。
  3. 主数据存储类是Dictionary 。 我们编写了自己的字典 ,它不存储任何哈希值 – 结果是内存使用率在64位系统上减少了3倍,在32位系统上减少了2倍。

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

您的PeakWorkingSet指示32位CLR开始轰炸时的公共号码。

尽管人们告诉你,尽管有自动内存管理的巨大讽刺,你必须意识到这一点,并确保你永远不会在这样的/ 32位系统上达到这个限制。 许多人都没有意识到这一点,我通常喜欢拿起他们的C#臃肿downvotes,但是当你在一个桌面上运行一些这样的应用程序时,你可能会造成一些严重的破坏。 只需看看VS关机的管理部分,它就像是通过PC运行的火车。

有一个免费的MemProfiler for .NET,使用它并寻找悬挂的根源..最终,特别是当你开始处理中等大小的数据时,你将不得不使用设计流而不是依赖它将在x64上运行更多内存。

如今,拥有一个c880MB数据集的规模很可怜..事实!

[片到C#3.0羊]

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

已经提出了许多有用的解决方案,MSDN文章非常详尽。 结合上述建议,我也会做以下事情;

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

exception之前的内存使用量是否会在很长一段时间内逐渐增加(例如,无限期运行的服务器进程),或者它是否会大幅增加,直到exception为止? 有很multithreading正在运行还是只有一个?

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

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

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

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

不鼓励在之前的评论中明确地调用垃圾收集器。 虽然你应该很少这样做,但有时它是有效的(搜索Rico Mariani的博客的例子)。 我明确调用collect的一个例子(在博客中提到)是从服务返回大量字符串,放入数据集然后绑定到网格的时候。 即使在屏幕关闭后,这段内存也没有收集一段时间。 一般情况下,它不应该被显式调用,因为垃圾收集器维护它所基于(以及其他)集合的度量。 调用collect显式使这些指标无效。

最后,了解应用程序的内存要求通常很好。 要么通过记录更多信息,偶尔运行分析器,压力/单元/集成测试来获得此信息。 了解特定操作对高级别的影响,例如,基于一组大约x将被分配的输入。 通过在日志文件中的关键点记录详细信息,我了解了这一点。 膨胀的日志文件可能难以理解或解释。