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

Igo*_*hov 36 .net c#

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

我处理它的专业人士是:

  • 抛出OutOfMemoryException这一事实通常不意味着程序的状态已损坏;
  • 根据文档"下面的Microsoft中间(MSIL)指令抛出OutOfMemoryException:box,newarr,newobj"这只是(通常)意味着CLR试图找到给定大小的内存块并且无法做到这一点; 它意味着没有单字节留在我们的性格;

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

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

编辑:你认为OOME和ExecutionEngineException一样致命吗?

Mar*_*ell 35

国际海事组织,因为你无法预知你可以/不可以的OOM之后做什么(所以你不能可靠地处理错误),还是什么别的展开堆栈你在哪里什么时候/没有发生(这样BCL没有可靠地处理错误),现在必须假定您的应用程序处于损坏状态.如果你通过处理这个例外来"修复"你的代码,那么你就会把头埋在沙子里.

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

如果你需要处理一些数据的机会,并且很高兴它失败:启动第二个过程(一个AppDomain不够好).如果它爆炸,拆掉这个过程.问题解决了,您的原始流程/ AppDomain是安全的.

  • @Marc Gravell:我认为*有时*我们可以预测我们能做些什么.想象一下当我们调用无法控制的第三方库方法时的情况,但我们知道可能消耗大量内存.如果我们捕获OOM,那么我们将有几个选择,不是吗?是不是只是让我们的用户用可怜的屏幕说"应用程序遇到错误并需要关闭?" (4认同)

Mat*_*ted 21

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

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

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

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

要编辑...

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

但与此同时,由于SQL注入,软件的不同步版本,指针操作,缓冲区运行以及许多其他问题导致数据损坏的可能性也同样可能.避免只因为你一个问题,觉得可能无法从中恢复是为用户提供错误信息的建设性作为一个伟大的方式,请与系统管理员联系.


Chr*_*n.K 18

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

  • 还应注意,[InsufficientMemoryException](http://msdn.microsoft.com/en-us/library/system.insufficientmemoryexception.aspx)的MSDN文章指出:`与OutOfMemoryException不同,在开始操作之前抛出InsufficientMemoryException,因此并不意味着国家腐败.应用程序可以捕获此异常,减少其内存使用量,并避免实际的内存不足情况及其破坏程序状态的可能性. (3认同)

Chr*_*isF 13

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

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

如果几何体> 2GB,我们仍然会遇到问题,但这是一个不同的故事.

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


Ser*_*kov 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案例作为风险较小的案件,您正在为那一天做准备.


Aar*_*ght 8

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

异常处理的核心原则之一是永远不会在异常处理程序中抛出异常. (注意 - 重新抛出特定于域和/或包装的异常是正常的;我在这里谈论一个意外的异常.)

您需要阻止这种情况发生的原因有多种:

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

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

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

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

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

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

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

之前提出给我的一个论点是你可能会捕获一个OutOfMemoryException然后切换到较低内存的代码,比如较小的缓冲区或流模型.然而,这种" 预测处理 "是一种众所周知的反模式.如果你知道你要嚼大量的内存并且不确定系统是否可以处理它,那么检查可用的内存,或者更好的是,只需重构你的代码以便它不需要这么多的记忆一下子.不要依赖于OutOfMemoryException为你做这件事,因为 - 谁知道 - 也许分配只是勉强成功并异常处理程序之后立即触发一堆内存不足错误(可能在一些完全不同的组件中).

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

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


Dan*_*llo 6

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

  • @Aaronaught:你对此完全正确,但为什么你不同意我的观点那么简单:如果你尝试,你可能会成功.或者你不会.但如果你不尝试,你肯定不会成功. (2认同)