如何使JVM在任何OutOfMemoryException时退出,即使有人试图捕获它?

23

OOME属于一类通常不应该从中恢复的错误。但如果它被嵌在一个线程中,或者有人捕获了它,那么应用程序可能会进入一种既不退出也不有用的状态。即使面对可能愚蠢地尝试捕获Throwable或Error/OOME的库,如何防止这种情况发生?(也就是说,您无法直接访问修改源代码)有什么建议吗?


3
为什么不应该从中恢复? OOME不是由编程错误(如空指针或非法参数)引起的,而是因为在运行时出现了未预见到的情况,对于真正稳定的应用程序来说,应该尝试生存下来。当然,在发生这种情况时,通知管理员(在服务器应用程序中)是明智的,以便他们可以进行调查。 - Bart van Heukelom
2
@Bart - 我非常确定你建议的正是人们不应该做的事情(除非在非常特殊的情况下)。您可以阅读有关此问题的Java文档以获取更多详细信息。 - Michael Neale
不要和那种人一起工作? - Raedwald
@BartvanHeukelom 请参见 https://dev59.com/R3RC5IYBdhLWcg3wVvYt - Raedwald
10个回答

36

解决方案:

在较新的JVM上:

-XX:+ExitOnOutOfMemoryError
to exit on OOME, or to crash:

-XX:+CrashOnOutOfMemoryError

在旧版本中:

-XX:OnOutOfMemoryError="<cmd args>; <cmd args>"

定义:在首次抛出OutOfMemoryError时运行用户自定义命令。 (引入于1.4.2更新12、6版本)

请参见http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

一个杀死正在运行进程的示例:

-XX:OnOutOfMemoryError="kill -9 %p"

嗯...这可能行得通,取决于你想让命令做什么。报告错误是可以的,但kill -9终止JVM可能会产生不良副作用。 - Stephen C
你可以发送SIGTERM信号并等待...如果它没有停止就强制杀掉。 - Michael Neale
2
如果您在类Unix系统上运行,可以在-XX:OnOutOfMemoryError="killparent"中使用此命令https://gist.github.com/xylifyx/5865113 - Erik Martino
2
作为编译killparent程序的替代方案,可以考虑使用PPID shell变量,例如-XX:OnOutOfMemoryError="kill -9 $PPID" - Po' Lazarus
3
从Java 8u92开始,您可以使用-XX:+ExitOnOutOfMemoryError或-XX:+CrashOnOutOfMemoryError。 - Dennie

8
如果应用程序的JVM中的某些代码决定尝试捕获OOME并尝试恢复,那么您无法阻止它...除了可能不切实际且肯定会影响应用程序性能和可维护性的AOP英雄主义。 除此之外,您所能做的最好的事情就是使用"OnOutOfMemoryError"钩子关闭JVM。请参见上面的答案:https://dev59.com/eW865IYBdhLWcg3wUs_z#3878199/ 基本上,您必须相信其他开发人员不会做愚蠢的事情。其他您可能不应该试图防御的愚蠢行为包括:
- 在库方法中调用System.exit()深入, - 调用Thread.stop()等, - 泄漏打开的流、数据库连接等, - 创建大量线程, - 随机压制(即捕获并忽略)异常, - 等等。
在实践中,检查由其他人编写的代码中出现的此类问题的方法是使用代码质量检查器并进行代码审核。
如果问题出现在第三方代码中,请将其报告为BUG(它可能是),如果他们不同意,请开始寻找替代方案。
对于那些还不知道这一点的人,有许多原因不建议尝试从OOME中恢复:
- OOME可能是在当前线程正在更新某些重要数据结构时抛出的。在一般情况下,捕获此OOME的代码无法知道这一点,如果它尝试“恢复”,则有风险使应用程序继续使用受损的数据结构。 - 如果应用程序是多线程的,则可能在其他线程上也抛出了OOME,使恢复变得更加困难。 - 即使应用程序能够在不留下不一致状态的数据结构的情况下恢复,恢复也可能只会让应用程序再瘸几秒钟,然后再次出现OOME。 - 除非您适当地设置JVM选项,否则几乎耗尽内存的JVM倾向于花费大量时间进行垃圾回收以保持运行。尝试从OOME中恢复可能会延长痛苦。 从OOME中恢复对解决根本原因没有任何帮助,这通常是内存泄漏、设计不良(即浪费内存的)数据结构和/或使用过小的堆启动应用程序。

1
我认为原帖作者已经清楚地意识到捕获OOME是不明智的做法;他可能只是遇到了这种情况,想让整个系统关闭而不是继续运行。无论如何,强调这些要点总是好的 :) - biasedbit
Thread.currentThread().setUncaughtExceptionHandler(); 比AOP直截了当,但也更少捕获异常的机会。 - biasedbit
重新阅读这个回答,它似乎完全没有基于问题的阅读。第一段明显是错误的,因为其他许多答案都已经证明了这一点,而最后一段则恰好说明了问题所问的内容。我不明白(也许我之前编辑过问题,然后忘记了)。 - Michael Neale
它可能看起来像是这样。但实际上我确实阅读并完全理解了问题。除了大多数建议实际上并没有解决无节制 OOME 捕获的问题之外,第一段明显是错误的,基于许多其他答案......唯一真正解决这个问题的是要么在 OOME 抛出时关闭 JVM,要么在 OOME 抛出之前就关闭。 - Stephen C
你正在断章取义。上下文应该从“对于那些还不知道的人来说…”开始,最后一句话明确了问题所在。 - Stephen C
显示剩余7条评论

2
  1. 编辑 OutOfMemoryError.java 文件,在其构造函数中添加 System.exit()

  2. 编译文件。(有趣的是,javac 不在意它是否在 java.lang 包中)

  3. 将该类添加到 JRE 的 rt.jar 中。

  4. 现在 JVM 将使用这个新类。(邪恶的笑声)

这是您可能需要了解的一种可能性。它是否是一个好主意,或者甚至是否合法,是另一个问题。


2
这是合法的,但在生产代码中你永远不会想要这样做。 - Stephen C
1
有趣的是,javac并不在意它是否在java.lang包中。那么你怎么编译java.lang中的Java代码呢?是的,有安全检查来防止普通应用程序JAR(等)替换“java.lang.*”等类,但这些必须由类加载器/安全管理器强制执行。(如果您依赖Java编译器执行强制执行,那么相对简单地进行颠覆。) - Stephen C

1

我能想到的另一件事(尽管我不知道如何实现)是在某种调试器中运行您的应用程序。我注意到,我的调试器可以在抛出异常时停止执行。

因此,也许可以实现某种执行环境来实现这一点。


你不想在调试器中运行生产代码。首先,我认为这样做会显著降低性能。 - Stephen C

1

0

你在代码中自行捕获 OOME,并使用 System.exit() 如何?


2
更深层次的catch会首先捕获它,不捕获的唯一区别是现在你将退出整个程序而不是仅退出线程。 - Bart van Heukelom

0

0

有一种可能性,我希望能够被说服放弃,那就是创建一个愚蠢的线程,它的工作是在堆上执行某些操作。如果它收到 OOME 错误 - 那么它将退出整个 JVM。

请告诉我这不是明智的做法。


我会专注于避免 OOME;由于它是不良实践的后果,因此对此问题没有优雅的解决方案。有以下几个选择:a) 删除 try-catch 语句(假设可以访问源代码)b) 使用替代库c) 报告问题并提交补丁d) 自己编写库。 - biasedbit
是的 - 当然我正在处理 OOME 问题 - 目前的问题已经解决了。但这是一个普遍的原则 - 我不希望由于错误的错误处理或延长 JVM 的死亡而使我的应用程序处于无用状态。 - Michael Neale

0

您可以使用MemoryPoolMXBean来在程序超过设置的堆分配阈值时收到通知。

我自己没有使用过它,但是通过设置分配阈值并在收到通知时调用System.exit(),应该可以以这种方式关闭程序。


-1

我能想到的唯一方法就是使用AOP来包装每个方法(注意排除java.*),用try-catch捕获OOME,如果出现这种情况,则记录日志并在catch块中调用System.exit()。

虽然不是我认为优雅的解决方案...


听起来很痛苦 ;) 而且没有办法区分哪些代码片段有良好的 OOME 捕获器,哪些有不好的。 - Chris Dennett
它并不会增加太多的开销。问题在于如果try-catch位于某个晦涩方法的中间,你无能为力。然而,如果异常发生在库/应用程序的某个较低级别处,并且某个较高级别的方法捕获了它,那么AOP将在这里起作用。就像我说的,我想不出其他可以解决这种情况的方法 :) - biasedbit
"... 你无能为力。" 其实可能还有办法……如果你知道发生这种情况的确切方法,AOP 可以帮忙:配置一个切入点来完全转移调用该方法的流程(大多数情况下这是不可能的,特别是需要上下文或使用实例变量的实例方法)。 - biasedbit
这确实会很痛苦,但主要是因为它不是完全正确的AOP解决方案。您可以编写切入点来拦截catch块,这意味着您的方面只需要包装catch(Throwable)和catch(OOME)。但无论如何,我认为这种努力的投资回报率很低。 - RonU

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接