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

时间:2009-11-05 05:48:34

标签: c# .net memory profiling

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

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) - 以及异常信息,日志和屏幕截图 - 并通过电子邮件发送给我所有内容。

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

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

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

8 个答案:

答案 0 :(得分:10)

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

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

  

CLR Profiler让您可以查看   进程的托管堆和   调查的行为   垃圾收集器。使用各种   在工具中查看,即可获得   有用的信息   执行,分配和记忆   消费你的申请。

     

使用CLR Profiler,您可以   识别分配太多的代码   内存,造成太多垃圾   集合,并保持记忆   太久了。

答案 1 :(得分:9)

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

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

有关使用WinDbg的提示,请查看this blog

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

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

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

  1. 有些数据存储在内存中几次(多次复制)。我为每一组数据制作了一份副本。
  2. 有些数据存储为string,但更有效的方法是int,因为这些字符串只包含数字。
  3. 主要数据存储类为Dictionary<uint,uint>We wrote our own dictionary不存储任何哈希值 - 结果是内存使用率在64位系统上减少了3倍,在32位系统上减少了2倍。
  4. 所以我的问题是:用于存储数据的主要类/对象是什么?您存储了哪种数据?

答案 5 :(得分:1)

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

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

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

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

[片到C#3.0绵羊]

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

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

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

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

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

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

时,这种方法不能很好地工作

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

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

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