Java中处理内存不足的最佳方法是什么?

11
我们有一个应用程序,它会生成新的JVM并代表用户执行代码。有时候应用程序会耗尽内存,并因此表现出非常不同的行为方式。有时它们会抛出OutOfMemoryError,有时会冻结。我们可以通过轻量级后台线程检测到后者,在内存不足时停止发送心跳信号。在这种情况下,我们杀死了JVM,但我们绝不能确定失败接收心跳的真正原因是什么。(也可能是网络问题或分段错误)。
最可靠的方法是什么来可靠地检测JVM中的内存不足状况?
- 理论上,-XX:OnOutOfMemoryError选项很有前途,但由于这个错误而实际上无法使用:https://bugs.openjdk.java.net/browse/JDK-8027434 - 捕获OutOfMemoryError实际上不是一个好的替代方案(例如您永远不知道它发生在哪里),尽管它在许多情况下确实起作用。 - 仍然存在一些情况,即JVM冻结并且不会抛出OutOfMemoryError。我仍然确定内存是这个问题的原因。
是否有任何其他选择或解决方法?垃圾收集设置使JVM终止自身而不是冻结?
编辑:我完全控制分支和分叉JVM以及其中执行的代码,两者都在Linux上运行,如果有帮助,可以使用特定于操作系统的工具。

2
听起来你真正感兴趣的是检测另一个进程中发生了内存不足的情况;这是你的问题标题甚至没有暗示到的关键点。 - supercat
谢谢。我尝试在帖子中将这一点表述得更清楚,但到目前为止,我还没有改变标题,因为我能想到的所有替代标题都会引起误解。特别是,我不介意我们是从JVM内部获取信息,从调用JVM获取信息,将其视为具有特定行为的JVM来观察,还是仅将其视为一个进程。 - Simon Fischer
2
如果您不改进标题,许多可能能够回答的人甚至不太可能打开您的帖子。也许“如果Java VM进程耗尽内存则触发警报”会是一个更好的标题? - supercat
“捕获OutOfMemoryError” - 我猜你已经在使用 setDefaultUncaughtExceptionHandler 了,对吧?理论上,当 OOME 发生时,您可以分配几兆字节并释放它们,以便错误处理程序有更好的存活机会。只是猜测... - maaartinus
有关为什么捕获OutOfMemoryError存在问题的更多信息,请参见https://dev59.com/omoy5IYBdhLWcg3wKq-s - Raedwald
3个回答

2

唯一的真正选择是(不幸的是)尽快终止JVM。

由于您可能无法更改所有代码以捕获错误并进行响应。如果您不信任OnOutOfMemoryError(我想知道为什么它不使用Java 8使用的vfork,并且在Windows上运行),您至少可以触发heapdump并在外部监视这些文件:

java .... -XX:+HeapDumpOnOutOfMemoryError "-XX:OnOutOfMemoryError=kill %p"

"-XX:+HeapDumpOnOutOfMemoryError" 实际上是一个我们尚未尝试的选项。目前为止,似乎这个选项也不可靠地创建。关于 "-XX:OnOutOfMemoryError":它确实有效,但仅当操作系统还有约50%的总内存可用时才有效,在这种情况下,我宁愿将其分配给JVM,而不是仅为此目的保留它 :-) - Simon Fischer
@SimonFischer 在我看来,JRE已经切换到vfork用于Runtime#exec(),但我不确定这是否也包括OnOutOfMemory。但当然,它可能无法在空间受限的条件下执行命令。我不确定CrashReporter服务器和操作系统崩溃报告是否为选项。毕竟,您需要检查消失的进程。 - eckes

1

经过一段时间的实验,我们找到了以下解决方案:

  1. 在创建的JVM中,捕获OutOfMemoryError并立即退出,在退出时向控制JVM发出退出码信号,表示内存不足的情况。
  2. 在创建的JVM中,定期检查当前Runtime所使用的内存量。当使用的内存数量接近临界值时,创建一个标志文件,向控制JVM发出内存不足的信号。如果我们从该条件下恢复并正常退出,则在退出之前删除该文件。
  3. 在控制JVM加入分叉的JVM之后,它检查步骤(1)生成的退出码和步骤(2)生成的标志文件。除此之外,它还会检查文件hs_err_pidXXX.log是否存在,并且是否包含"Out of Memory Error"的行。(如果Java崩溃,该文件将由Java生成。)

只有在实现了所有这些检查之后,我们才能处理分叉的JVM内存不足的所有情况。我们相信自那时起,我们没有错过任何这种情况。

Java标志-XX:OnOutOfMemoryError由于fork问题未被使用,-XX:+HeapDumpOnOutOfMemoryError也未被使用,因为堆转储超出了我们的需求。
解决方案肯定不是有史以来最优雅的代码,但对我们来说完成了工作。

你能解释一下“分叉问题”吗?我正在将它添加到我们的一个服务中,但是找不到更多关于分叉问题的信息。 - Claim
关于“分叉问题”的声明,我指的是原问题中第一个要点中链接的JDK错误票。当时,-XX:OnOutOfMemoryError是无法使用的,但该错误票表示该问题已在2018年得到解决。 - undefined

0
如果您对应用程序和配置都有控制权,最好的解决方案是找到导致OutOfMemoryError抛出的根本原因并修复它,而不是尝试通过捕获错误或仅重新启动JVM来隐藏症状。
从您所描述的情况来看,JVM上运行的应用程序可能存在内存泄漏,可能是因为使用了资源不足(在您的情况下是内存),也可能偶尔处理需要异常大块的堆事务。对于这些情况,解决方案会有所不同:
1. 如果存在内存泄漏,请找到根本原因并让工程师进行修复。工具包括堆转储分析器、分析器或泄漏检测器。 2. 如果存在资源不足,请监视应用程序内存消耗,例如通过垃圾收集日志,并根据面临的情况调整不同内存池的大小。 3. 如果用户事务期间存在激增分配,则需要跟踪导致激增的代码,并让工程师修复它-通过禁用某些用户输入或以较小的批次加载和处理数据。进程的线程转储或堆转储可以引导您找到解决方案。

从问题中我推断出OutOfMemory没有单一的原因,只是运行任意用户代码。我认为他并不是在问如何修复用户的代码。 - matt freake
没错。人们向我们提交工作流程(将其视为可视化编程)。我们在分叉的JVM中运行它们。如果这样的工作流程在选择在8 GB机器上执行16 GB数据文件的矩阵操作时,这是不可能的。调用者必须修复它,但我们需要告诉他们,内存是问题所在,而不是我们端口上的JVM错误或其他错误。 - Simon Fischer
在这种情况下,Plumbr(https://plumbr.eu)将会做到这一点 - 例如,在内存泄漏的情况下,您将获得确切的根本原因,您可以将其发送给工程师,他们可以立即缩小到潜在问题,因为来自Plumbr的事件报告会指向导致问题的源代码中的确切行。 - Ivo

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