什么时候可以捕获OutOfMemoryException以及如何处理它?

时间:2010-01-22 12:27:24

标签: c# .net

昨天我参加了关于SO专门讨论OutOfMemoryException以及处理它的优缺点的讨论(C# try {} catch {})。

我处理它的优点是:

  • 抛出OutOfMemoryException这一事实通常并不意味着程序的状态已损坏;
  • 根据文档“以下Microsoft中间(MSIL)指令抛出OutOfMemoryException:box,newarr,newobj”只是(通常)意味着CLR试图找到给定大小的内存块而无法执行此操作; 意味着我们的处置不会留下任何字节;

但并非所有人都同意这一点,并推测在此异常之后未知的程序状态以及无法做一些有用的事情,因为它需要更多的内存。

因此我的问题是:没有处理OutOfMemoryException并在发生时立即放弃的严重原因是什么?

编辑:您认为OOME与ExecutionEngineException一样致命吗?

10 个答案:

答案 0 :(得分:35)

IMO,因为你不能预测在OOM之后你能做什么/不能做什么(所以你不能可靠地处理错误),或者 else 在将堆栈展开到您所在的位置时(确实/没有发生)(因此BCL未能可靠地处理错误),您的应用程序现在必须假定处于损坏状态。如果你通过处理这个异常来“修复”你的代码,那么你就会把头埋在沙子里。

我可能在这里错了,但对我而言,这个消息说是大麻烦。正确的解决方法是弄清楚你为什么要通过内存来解决问题并解决这个问题(例如,你有泄漏吗?你可以切换到流API吗?)。即使切换到x64也不是一个神奇的子弹;数组(以及列表)仍然是大小有限的;并且增加的引用大小意味着您可以在数字上修复2GB对象上限中较少的引用。

如果您需要偶然处理某些数据,并且对它失败感到高兴:启动第二个过程(AppDomain不够好)。如果它爆炸,拆掉这个过程。问题已解决,原始流程/ AppDomain是安全的。

答案 1 :(得分:21)

我们都写不同的应用程序。在WinForms或ASP.Net应用程序中,我可能只记录异常,通知用户,尝试保存状态,并关闭/重新启动。但正如Igor在评论中提到的,这很可能来自于构建某种形式的图像编辑应用程序,并且加载第100个20MB RAW图像的过程可以将应用程序推到边缘。你是否真的希望使用这些东西从简单的说法中丢失所有的工作。 “抱歉,此时无法加载更多图片”。

捕获内存不足可能有用的另一个常见实例是后端批处理。你可以有一个将多兆字节文件加载到内存中进行处理的标准模型,但是有一天会加载一个数千字节的文件。发生内存不足时,您可以将消息记录到用户通知队列,然后转到下一个文件。

是的,其他东西可能会同时爆炸,但如果可能的话,也会记录并通知。如果最终GC无法处理更多内存,那么应用程序无论如何都会变得很难。 (GC在未受保护的线程中运行。)

不要忘记我们都开发了不同类型的应用程序。除非你使用旧的,受限制的机器,否则你可能永远不会为典型的商业应用程序获得OutOfMemoryException ...但是,我们所有人都不是商业工具开发人员。

要编辑...

内存不足可能是由非托管内存碎片和固定引起的。它也可能是由大量分配请求引起的。如果我们在这样简单的问题上竖起一面白旗并画上一条线,那么在大型数据处理项目中什么都不会做。现在将它与一个致命的引擎异常进行比较,那么在运行时在您的代码下失效的情况下,您无法做任何事情。希望你能够记录(但可能不是)为什么你的代码落在了它的脸上,所以你可以在将来阻止它。但是,更重要的是,希望您的代码编写方式能够安全地恢复尽可能多的数据。甚至可能会恢复应用程序中最后一个已知的良好状态,并可能跳过有问题的损坏数据并允许手动处理和恢复。

然而同时,由于SQL注入,软件的不同步版本,指针操作,缓冲区运行以及许多其他问题导致数据损坏的可能性也同样可能。仅仅因为认为 而从中恢复而避免问题是一种很好的方式来向用户提供错误消息,因为请与您的系统管理员联系

答案 2 :(得分:18)

有些评论者指出,有些情况下,OOM可能是尝试分配大量字节(图形应用程序,分配大型数组等)的直接结果。请注意,为此目的,您可以使用MemoryFailPoint类,它会引发InsufficientMemoryException(本身派生自OutOfMemoryException)。可以安全地捕获 ,因为在实际分配内存的尝试之前已经引发了。但是,这只能真正降低OOM的可能性,永远不会完全阻止它。

答案 3 :(得分:13)

这一切都取决于具体情况。

几年前,我正在开发一个实时3D渲染引擎。当我们在启动时将模型的所有几何图形加载到内存中时,但只在我们需要显示它们时才加载纹理图像。这意味着当一天到来时,我们的客户正在装载我们能够应对的大型(2GB)型号。几何体的占用小于2GB,但是当添加所有纹理时,它将是> 2GB。通过捕获我们尝试加载纹理时引发的内存不足错误,我们可以继续显示模型,但就像普通几何一样。

如果几何图形> gt,我们仍有问题。 2GB,但这是一个不同的故事。

显然,如果你的应用程序有一些基本的内存错误,那么你别无选择,只能关闭 - 但尽可能优雅地做到这一点。

答案 4 :(得分:10)

建议Christopher Brumme在“框架设计指南”第238页(7.3.7 OutOfMemoryException)中的评论:

在频谱的一端,OutOfMemoryException可能是因为无法获得12个字节以进行隐式自动装箱,或者无法JIT某些关键退出所需的代码。这些情况是灾难性的失败,理想情况下会导致流程终止。另一方面,OutOfMemoryException可能是一个线程要求1 GB字节数组的结果。我们未通过此分配尝试的事实对其余流程的一致性和可行性没有影响。

可悲的事实是,CRL 2.0无法区分此频谱上的任何点。在大多数托管进程中,所有OutOfMemoryExceptions都被认为是等效的,它们都会导致托管异常在线程中传播。但是,您不能依赖于正在执行的退出代码,因为我们可能无法JIT一些退出方法,或者我们可能无法执行退出所需的静态构造函数。

另外,请记住,如果没有足够的内存来实例化其他异常对象,则所有其他异常都可以折叠成OutOfMemoryException。另外,如果可以的话,我们将为您提供一个独特的OutOfMemoryException,它有自己的堆栈跟踪。但是如果我们对内存足够紧张,那么你将与其他所有人共享一个无趣的全局实例。

我最好的建议是将OutOfMemoryException视为任何其他应用程序异常。您尽最大努力处理它并保持一致。在未来,我希望CLR可以更好地区分灾难性OOM和1 GB字节数组的情况。如果是这样,我们可能会引发灾难性案件的终止流程,让申请处理风险较小的案件。通过威胁所有OOM案例作为风险较小的案件,你正在为那一天做准备。

答案 5 :(得分:8)

Marc Gravell已经提供了一个很好的答案;看到我如何“启发”这个问题,我想补充一点:

异常处理的核心原则之一是永远不会在异常处理程序中抛出异常。(注意 - 重新抛出特定于域的和/或包装的异常是正常的;我在说话关于意外的例外。)

为什么你需要防止这种情况发生,有各种各样的原因:

  • 充其量,你掩饰了原来的异常;无法确定程序最初失败的位置。

  • 在某些情况下,运行时可能只是无法处理异常处理程序中的未处理异常(比如说快5倍)。例如,在ASP.NET中,在管道的某些阶段安装异常处理程序并在该处理程序中失败将简单地终止请求 - 或者使工作进程崩溃,我忘记了。

  • 在其他情况下,您可能会在异常处理程序中打开无限循环的可能性。这可能听起来像是一件愚蠢的事情,但我已经看到有人试图通过记录来处理异常的情况,并且当记录失败时...他们尝试记录失败。我们大多数人可能不会故意编写这样的代码,但是根据你如何构建程序的异常处理,你最终可能会意外地完成它。

那么这与OutOfMemoryException具体有什么关系呢?

OutOfMemoryException并未告诉您有关内存分配失败的原因的任何信息。您可能假设这是因为您试图分配一个巨大的缓冲区,但可能不是。也许系统上的其他一些流氓进程实际上消耗了所有可用的地址空间,并且你没有剩下一个字节。也许你自己的程序中的其他一些线程出错并进入无限循环,在每次迭代时分配新的内存,并且该线程早在OutOfMemoryException结束时失败了你当前的堆栈框架。关键是你实际上并不知道内存情况有多糟糕,即使你认为你做了

现在开始考虑这种情况。有些操作只是在.NET框架内部的一个未指定的位置失败并向上传播OutOfMemoryException。您可以在不涉及分配更多内存的异常处理程序中执行哪些有意义的工作?写入日志文件?这需要记忆。显示错误消息?这需要更多的记忆。发送提醒电子邮件?甚至不要考虑它。

如果你尝试做这些事情 - 然后失败 - 那么你最终会得到非确定性的行为。您可能会掩盖内存不足错误,并从您编写的各种低级别组件中冒出的神秘错误消息中获取神秘的错误消息,这些错误消息不应该是失败的。从根本上说,你违反了自己程序的不变量,如果你的程序最终在低内存条件下运行,这将是一个调试的噩梦。

之前向我提出的一个论点是,您可能会捕获OutOfMemoryException,然后切换到较低内存的代码,如较小的缓冲区或流模型。然而,这个“Expection Handling”是众所周知的反模式。如果您知道自己即将咀嚼大量内存并且不确定系统是否可以处理它,那么check the available memory或者更好的是,只需重构您的代码,这样就不会一下子需要这么多的记忆。不要依赖OutOfMemoryException为你做这件事,因为 - 谁知道 - 也许分配几乎成功并立即触发一堆内存不足错误 之后的异常处理程序(可能在一些完全不同的组件中)。

所以我对这个问题的简单回答是:从不。

我对这个问题的狡猾回答是:如果你真的非常小心,那么在全局异常处理程序中它是可以的。不在try-catch块中。

答案 6 :(得分:6)

捕获此异常的一个实际原因是尝试正常关闭,并使用友好的错误消息而不是异常跟踪。

答案 7 :(得分:2)

问题比.NET大。如果没有可用的内存,几乎任何从五十年代写到现在的应用程序都有很大的问题。

使用虚拟地址空间时,问题已被抢救但尚未解决,因为即使是2GB或4GB的地址空间也可能变得太小。没有通常可用的模式来处理内存不足。可能存在内存不足警告方法,恐慌方法等,保证仍有内存可用。

如果从.NET收到OutOfMemoryException,几乎可能出现任何情况。 2 MB仍然可用,只有100个字节,无论如何。我不想捕获此异常(除非在没有失败对话框的情况下关闭)。我们需要更好的概念。然后你可能会得到一个MemoryLowException,你可以对各种情况做出反应。

答案 8 :(得分:1)

问题在于 - 与其他异常相反 - 当异常发生时,您通常会遇到内存不足的情况(除非要分配的内存很大,但您实际上并不知道何时捕获异常)。

因此,在处理此异常时,必须非常小心不要分配内存。虽然这听起来很容易但实际上很难避免任何内存分配并做一些有用的事情。因此,捕捉它通常不是一个好主意恕我直言。

答案 9 :(得分:-2)

编写代码,不要劫持JVM。当VM谦虚地告诉您内存分配请求失败时,最好的办法是放弃应用程序状态以避免破坏应用程序数据。即使您决定捕获OOM,您也应该只尝试收集转储日志,堆栈跟踪等诊断信息。请不要尝试启动退出程序,因为您不确定它是否有机会执行。

现实世界的比喻:你在飞机上旅行,所有引擎都失败了。捕获AllEngineFailureException后你会怎么做?最好的办法是抓住面具,为撞车做准备。

在OOM中,转储!!