是“内存不足”一个可恢复的错误?

我已经编程了很长一段时间,我看到的程序在内存不足时试图清理并退出,即优雅地失败。 我不记得上次我看到一个实际上试图恢复并继续正常运行。

如此多的处理依赖于能够成功地分配内存,特别是在垃圾收集语言中,看来内存不足错误应该被归类为不可恢复的。 (不可恢复的错误包括堆栈溢出等)。

什么是令人信服的论据,使其成为一个可恢复的错误?

这真的取决于你在build什么。

networking服务器对一个请求/响应对进行失败并不是完全不合理的,然后继续进行进一步的请求。 你必须确定,单一的失败对全球国家没有不利的影响,然而 – 这将是棘手的一点。 鉴于失败在大多数托pipe环境(例如.NET和Java)中导致exception,我怀疑如果exception是在“用户代码”中处理的,那么将来的请求可以恢复 – 例如,如果一个请求试图分配10GB的内存,失败了,那应该不会损害系统的其他部分。 如果在尝试将请求交给用户代码时系统内存不足,那么这种事情可能会更糟糕。

在一个库中,你想有效地复制一个文件。 当你这样做的时候,你通常会发现使用less量大块进行复制比复制大量小块要有效得多(比如复制一个15MB大小的文件比复制15,000大块要快15% 1K块)。

但代码适用于任何块大小。 因此,虽然1MB块的速度可能更快,但是如果您devise了复制大量文件的系统,那么捕获OutOfMemoryError并减小块大小可能是明智之举,直到您成功为止。

另一个地方是存储在数据库中的Object的caching。 您希望尽可能多地保留caching中的对象,但不希望干扰其他应用程序。 由于可以重新创build这些对象,因此节省内存以便将caching连接到内存不足处理程序以删除条目,直到应用程序的其余部分有足够的空间再次呼吸为止。

最后,对于image processing,您希望尽可能多地将图像加载到内存中。 同样,一个OOM处理程序允许您在事先不知道用户或操作系统将授予您的代码的情况下执行多less内存。

[编辑]请注意,我在这里假设您已经给应用程序一个固定数量的内存,这个数量比不包括交换空间的可用内存总量要小。 如果你可以分配太多的内存,那么它的一部分就不得不被换掉,我的一些评论就没有意义了。

MATLAB的用户在大数组执行算术时,一直在内存不足。 例如,如果variablesx适合内存,并运行“x + 1”,那么MATLAB为结果分配空间,然后填充它。 如果分配失败MATLAB错误,用户可以尝试其他的东西。 如果MATLAB在这个用例出现的时候退出,那将是一场灾难。

OOM应该是可恢复的,因为关机不是从OOM恢复的唯一策略。

实际上,在应用程序级别上有一个非常标准的OOM问题解决scheme。 作为应用程序devise的一部分,确定从内存不足状态恢复所需的最小安全内存量。 (例如自动保存文档所需的内存,调出警告对话框,logging关机数据)。

在应用程序的开始或关键块的开始时,预先分配该内存量。 如果您检测到内存不足的情况,请释放警戒内存并执行恢复。 这个策略仍然可能失败,但总的来说,这是一笔巨款。

请注意,该应用程序不需要closures。 它可以显示一个模式对话框,直到OOM条件解决。

我不是100%肯定的,但我很确定' 代码完成 '(任何可敬的软件工程师必读)涵盖了这一点。

PS您可以扩展您的应用程序框架来帮助实施这一策略,但是请不要在图书馆实施这样的政策(好的图书馆在未经申请同意的情况下不会做出全球决定)

我正在为IOcaching分配内存以提高性能的系统上工作。 然后,在检测到OOM的时候,它会把它取回来,这样业务逻辑就可以继续下去,即使这意味着更less的IOcaching和稍微低的写入性能。

我还与embedded式Java应用程序合作,试图通过强制垃圾收集来pipe理OOM,可选地释放一些非关键对象,如预取或caching的数据。

OOM处理的主要问题是:

1)能够在发生的地方重新尝试,或者能够从更高的位置回滚并重新尝试。 大多数当代程序过于依赖语言来抛出,并没有真正pipe理它们到底在哪里以及如何重新尝试操作。 如果操作的devise不被保留,通常操作的上下文将会丢失

2)能够实际释放一些内存。 这意味着一种资源pipe理器,它知道哪些对象是关键的,哪些不是关键的,并且系统能够在他们以后变得关键的时候重新请求被释放的对象

另一个重要的问题是能够在不引发又一个OOM情况的情况下回滚。 这在高级语言中很难控制。

另外,底层操作系统必须对OOM有预见性。 例如,如果启用了内存过量使用,Linux就不会。 许多支持交换的系统会比将OOM报告给违规应用程序更快。

而且,在这种情况下,不是你的过程造成了这种情况,所以如果违规过程继续泄漏,释放记忆就无济于事。

因为这一切,通常是使用这种技术的大型embedded式系统,因为他们可以控制操作系统和内存来实现它们,以及实现这些技术的学科/动机。

我认为就像许多事情一样,这是一个成本/收益分析。 你可以尝试从malloc()失败中恢复,虽然这可能很困难(你的处理程序最好不要陷入和它相同的内存不足的问题)。

您已经注意到,最常见的情况是清理并优雅地失败。 在这种情况下,已经决定优先中止的成本低于开发成本和恢复中的性能成本的组合。

我相信你可以想一想自己的例子,在这种情况下终止程序是一个非常昂贵的select(生命支持机器,飞船控制,长期运行和时间关键的财务计算等) – 虽然第一道防线是当然要确保程序具有可预测的内存使用情况,并确保环境能够提供这种使用。

只有当你抓住它并正确处理它时,它才能被恢复。

例如,在相同的情况下,请求试图分配大量内存。 这是相当可预见的,你可以很好地处理它。

但是,在multithreading应用中的很多情况下,OOE也可能发生在后台线程上(包括由系统/第三方库创build的)。 预测几乎是不可能的,你可能无法恢复所有线程的状态。

不可以。从GC出来的内存不足错误通常不应在当前线程内恢复。 (可恢复线程(用户或内核)的创build和终止应该被支持)

关于计数器的例子:我目前正在研究一个使用NVIDIA的CUDA平台进行GPU计算的D编程语言项目。 我没有手动pipe理GPU内存,而是使用D的GC来创build代理对象。 所以,当GPU返回内存不足错误时,我运行一个完整的收集,如果第二次失败,只会引发一个exception。 但是,这不是真正的内存恢复的例子,它更多的是GC集成。 其他的恢复例子(caching,自由列表,自动收缩的堆栈/哈希等)都是有自己的方法来收集/压缩内存,这些方法是独立于GC的,并且往往不在本地分配function。 所以人们可能会执行如下的操作:

 T new2(T)( lazy T old_new ) { T obj; try{ obj = old_new; }catch(OutOfMemoryException oome) { foreach(compact; Global_List_Of_Delegates_From_Compatible_Objects) compact(); obj = old_new; } return obj; } 

一般来说,添加支持向垃圾收集器注册/注销自收集/压缩对象是一个不错的参数。

在一般情况下,这是不可收回的。

但是,如果您的系统包含某种forms的dynamiccaching,则内存不足处理程序通常会转储caching中最老的元素(甚至是整个caching)。

当然,您必须确保“转储”过程不需要新的内存分配:)另外,恢复失败的特定分配可能会非常棘手,除非您能够直接在分配器上插入caching转储代码级别,以便失败不会传播给调用者。

这取决于你的内存不足意味着什么。

malloc()在大多数系统上失败时,这是因为你已经用完了地址空间。

如果大多数内存是通过caching或mmap的区域来获取的,则可以通过释放caching或取消对其进行回收。 然而这真的需要你知道你在用什么内存 – 而且你已经注意到大多数程序没有,或者没有什么区别。

如果你对自己使用了setrlimit() (为了防止未被发现的攻击,也许,或者也许root做了你),你可以放松你的error handling程序的限制。 我经常这样做,在可能的情况下提示用户并logging事件。

另一方面,捕获堆栈溢出有点困难,并且不便携。 我为ECL写了一个posixish解决scheme,并描述了一个Windows实现,如果你要走这条路线。 几个月前它被检入ECL,但是如果你有兴趣,我可以挖掘原始的补丁。

这个问题被标记为“与语言无关”,但是在不考虑语言和/或底层系统的情况下很难回答。 (我看到几个前哨子

如果内存分配是隐含的,没有机制来检测给定的分配是否成功,那么从内存不足状态恢复可能是困难的或不可能的。

例如,如果您调用一个试图分配巨大数组的函数,那么大多数语言只是在数组无法分配时才定义行为。 (在Ada中,这会引发Storage_Errorexception,至less在原则上应该可以处理。)

另一方面,如果你有一个机制试图分配内存,并能够报告失败(如C的malloc()或C ++的new ),那么是的,这当然有可能从这个失败中恢复。 至less在malloc()new的情况下,失败的分配除了报告失败之外不会做任何事情(例如,它不会破坏任何内部数据结构)。

尝试恢复是否合理取决于应用程序。 如果应用程序在分配失败后无法成功,则应该执行任何清理操作并终止。 但是,如果分配失败仅仅意味着一个特定的任务不能被执行,或者如果任务仍然可以用较less的内存更慢地执行,则继续操作是有意义的。

一个具体的例子:假设我正在使用文本编辑器。 如果我试图在需要大量内存的编辑器中执行一些操作,并且操作无法执行,我希望编辑器告诉我不能执行我所要求的操作并让我继续编辑 。 终止而不拯救我的工作将是一个不可接受的回应。 保存我的工作和终止会更好,但仍然是不必要的用户敌对。

这是一个困难的问题。 初看起来,似乎没有更多的记忆手段“运气不好”,但你也必须看到,如果真的坚持,可以摆脱许多记忆相关的东西。 我们只是采取其他方式打破functionstrtok一方面没有记忆的东西问题。 然后从Glib库中取出g_string_split作为对手,这很大程度上取决于内存的分配,几乎所有在glib或基于GObject的程序中都是这样。 人们可以明确地说,在更dynamic的语言中,内存分配更多地用在更灵活的语言中,特别是C中。但让我们看看替代scheme。 如果您只是在内存不足的情况下结束程序,则即使仔细开发的代码也可能会停止工作。 但是,如果你有一个可恢复的错误,你可以做一些事情。 因此,使其可以恢复的观点意味着人们可以select“处理”不同的情况(例如,为紧急情况留出内存块,或者降低内存的广泛程序)。

所以最令人信服的原因是。 如果你提供了一种恢复方式,可以尝试恢复,如果你没有select所有依赖总是得到足够的内存… … –

问候

现在只是让我困惑。

在工作中,我们有一组应用程序一起工作,内存不足。 虽然问题是要么应用程序捆绑去64位(所以,能够超出我们在一个正常的Win32操作系统上的2Go限制),和/或减less我们的内存使用,这个问题“如何从OOM恢复“不会退缩。

当然,我没有办法解决,但仍然在为C ++寻找一个(主要是因为RAII和exception)。

也许一个应该适度地恢复的进程应该在primefaces/回滚能够完成的任务中分解它的处理(也就是说,只使用强/不合逻辑exception保证的函数/方法),并且为恢复目的保留一个“缓冲区/内存池”。

如果其中一个任务失败,C ++ bad_alloc将展开堆栈,通过RAII释放一些堆栈/堆内存。 然后,恢复function将尽可能地挽救(保存磁盘上的任务的初始数据,以便稍后尝试使用),并可能注册任务数据以供稍后尝试使用。

我相信使用C ++ strong / nothrow guanrantees可以帮助一个进程在低可用内存条件下生存,即使它是相同的内存交换(即慢,有些没有响应等),但当然,这是只有理论。 在试图模拟这个问题之前(即创build一个C ++程序,使用有限内存的自定义新/删除分配器,然后尝试在这些压力条件下做一些工作),我只需要在这个主题上变得更聪明。

好…

特别是在垃圾收集的环境中,如果你在应用程序的高层次上捕捉到OutOfMemory错误,很可能会有很多东西超出范围,可以回收给你回忆。

在单一的过度分配的情况下,应用程序可能能够继续完美地工作。 当然,如果你有一个内存泄漏的问题,你就会再次遇到问题(更可能是迟一点),但给应用程序一个优雅的机会,保存未保存的更改GUI应用程序的情况等

是的,OOM是可以恢复的。 作为一个极端的例子,大多数情况下,Unix和Windows操作系统都能很好地从OOM环境中恢复。 应用程序失败,但操作系统仍然存在(假设有足够的内存让操作系统正常启动)。

我只举这个例子来表明它可以完成。

处理OOM的问题实际上取决于你的程序和环境。

例如,在许多情况下,OOM发生的可能性最大的地方并不是从OOM状态实际恢复的最佳地点。

现在,自定义分配器可能可以作为可以处理OOM的代码中的一个中心点工作。 Java分配器在执行完全GC之前实际上会引发OOMexception。

分配器的“应用感知”越多,它就越适合作为OOM的中央处理器和恢复代理。 再次使用Java,它的分配器不是特别的应用程序意识。

这是像Java这样的东西很容易令人沮丧。 你不能覆盖分配器。 所以,虽然你可以在自己的代码中捕获OOMexception,但是没有什么可以说你正在使用的库正确地陷阱,或者甚至正确地抛出OOMexception。 创build一个永远被OOMexception破坏的类是微不足道的,因为一些对象被设置为null和“永远不会发生”,并且它永远不可恢复。

所以,是的,OOM是可以恢复的,但是它可能非常困难,特别是在像Java这样的现代环境中,以及各种各样质量的第三方库。

内存不足通常意味着你不得不放弃你所做的一切。 但是,如果您清理干净,它可以使程序本身运行并能够响应其他请求。 最好有一个程序说“对不起,没有足够的内存”,而不是说“抱歉,内存不足,closures”。

内存不足可能是由于空闲内存耗尽或试图分配不合理的大块(如一个演出)造成的。 在“耗尽”情况下,系统内存短缺是全球性的,通常会影响其他应用程序和系统服务,整个系统可能会变得不稳定,所以忘记和重新启动是明智的。 在“不合理的大块”情况下,实际上不存在短缺,继续下去是安全的。 问题是你不能自动检测你在哪个案件。所以这是更安全的,使错误不可恢复,并find一个解决scheme,你遇到这个错误的每一个案例 – 使你的程序使用较less的内存或在某些情况下只是修复调用内存分配的代码中的错误。

这里已经有很多很好的答案了。 但我想用另一个angular度来贡献。

一般而言,几乎任何可重用资源的耗尽应该是可恢复的。 原因是程序的每个部分基本上都是一个子程序。 只是因为一个子在这个时间点不能完成,并不意味着程序的整个状态都是垃圾。 只是因为停车场充满了汽车并不意味着你垃圾车。 你可以等一段时间,让摊位免费,或者开车到更远的地方去买你的cookies。

在大多数情况下,有一种替代方法。 使错误无法恢复,有效地消除了很多select,我们都不想让任何人为我们决定我们能做什么,不能做什么。

这同样适用于磁盘空间。 这是相同的推理。 与你有关堆栈溢出的暗示是相反的,我会说这是任意的限制。 没有很好的理由,你不应该抛出一个exception(popup很多帧),然后使用另一个效率较低的方法来完成工作。

我的两分钱:-)

如果你真的内存不足,你注定失败,因为你不能再释放任何东西。

如果你内存不足,但像垃圾收集器的东西可以踢,释放一些内存,你还没有死。

另一个问题是分裂。 虽然你可能不会内存不足(碎片化),但你仍然无法分配你想要的大块。

我知道你提出了争论,但是我只能看到反对意见。

我没有看到在multithreading应用程序中实现这一点。 你怎么知道哪个线程实际上对内存不足错误负责? 一个线程可以不断地分配新的内存,拥有99%的gc根,但是第一个失败的分配发生在另一个线程中。

一个实际的例子:当我们的Java应用程序(运行在JBoss服务器上)发生OutOfMemoryError时,它不会像一个线程死掉,服务器的其余部分继续运行:不,有几个OOME,杀死几个线程其中是JBoss的内部线程)。 我没有看到程序员可以做什么来恢复,甚至JBoss可以从中恢复。 实际上,我甚至不确定你是否可以这样做: VirtualMachineError的javadoc表明,在引发这样的错误之后,JVM可能会被“破坏”。 但也许这个问题更多的是针对语言devise。

当没有更多的内存需要dynamic分配时,uClibc有一个8字节左右的文件I / O的内部静态缓冲区。

什么是令人信服的论据,使其成为一个可恢复的错误?

在Java中,一个没有使其成为可恢复错误的引人注目的论据是因为Java允许OOM在任何时候被发信号通知,包括有时可能导致程序进入不一致的状态。 因此,来自OOM的可靠重制是不可能的。 如果发现OOMexception,则不能依赖任何程序状态。 请参阅无丢包VirtualMachineError保证

我有这个:

 void *smalloc(size_t size) { void *mem = null; for(;;) { mem = malloc(size); if(mem == NULL) { sleep(1); } else break; } return mem; } 

已经保存了几次系统了。 只是因为你现在的内存不足,并不意味着系统的其他部分或系统上运行的其他进程有一些内存,他们很快就会回来。 在尝试这样的技巧之前,你最好非常小心,并尽可能地控制你在程序中分配的每一个内存。