如何在Java中处理OutOfMemoryError?

2022-09-04 04:57:57

我必须序列化大约一百万个项目,当我运行代码时,我得到以下异常:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Unknown Source)
    at java.lang.String.<init>(Unknown Source)
    at java.io.BufferedReader.readLine(Unknown Source)
    at java.io.BufferedReader.readLine(Unknown Source)
    at org.girs.TopicParser.dump(TopicParser.java:23)
    at org.girs.TopicParser.main(TopicParser.java:59)

我该如何处理?


答案 1

我知道官方的Java答案是“哦不!没有记忆!我屈服了!对于任何在不允许内存不足成为致命错误的环境中进行编程的人来说,这都是相当令人沮丧的(例如,编写操作系统或为不受保护的操作系统编写应用程序)。

放弃的意愿是必要的 - 你无法控制Java内存分配的每个方面,所以你不能保证你的程序在内存不足的情况下会成功。但这并不意味着你必须不战而降。

不过,在战斗之前,你可以寻找避免这种需求的方法。也许您可以避免Java序列化,而是定义自己的数据格式,这不需要大量内存分配即可创建。序列化分配了大量内存,因为它保留了以前见过的对象的记录,因此如果它们再次发生,它可以按编号引用它们,而不是再次输出它们(这可能导致无限循环)。但这是因为它需要是通用的:根据你的数据结构,你可能能够定义一些文本/二进制/XML/任何可以写入流的表示形式,而几乎不需要存储额外的状态。或者,您可以安排所需的任何额外状态始终存储在对象中,而不是在序列化时创建。

如果您的应用程序执行一个使用大量内存的操作,但大多数操作使用较少,特别是如果该操作是用户启动的,并且如果您找不到使用较少内存或使更多内存可用的方法,那么可能值得捕获 OutOfMemory。您可以通过向用户报告问题太大并邀请他们修剪并重试来恢复。如果他们只是花了一个小时来设置他们的问题,你不想只是从程序中拯救出来并失去一切 - 你想给他们一个机会来解决这个问题。只要在堆栈上捕获错误,在捕获错误时将取消引用多余的内存,从而使 VM 至少有机会恢复。请确保在常规事件处理代码下面捕获错误(在常规事件处理中捕获 OutOfMemory 可能会导致忙循环,因为您尝试向用户显示对话框,但内存仍然不足,并且捕获了另一个错误)。仅围绕已标识为内存占用的操作捕获它,以便无法处理的 OutOfMemoryError(来自内存占用以外的代码)不会被捕获。

即使在非交互式应用程序中,放弃失败的操作也可能是有意义的,但是程序本身可以继续运行,处理进一步的数据。这就是为什么Web服务器管理多个进程,这样如果一个页面请求因内存不足而失败,服务器本身就不会掉线。正如我在顶部所说,单进程Java应用程序无法做出任何此类保证,但它们至少可以比默认的更强大一些。

也就是说,您的特定示例(序列化)可能不是此方法的良好候选者。特别是,用户在被告知存在问题时可能想要做的第一件事就是保存他们的工作:但是如果序列化失败,则可能无法保存。这不是你想要的,所以你可能不得不做一些实验和/或计算,并在它尝试序列化之前手动限制程序允许的百万个项目(基于它运行的内存量)。

这比尝试捕获错误并继续运行更强大,但不幸的是,很难计算出确切的界限,因此您可能不得不谨慎行事。

如果在反序列化过程中发生错误,那么您就处于更坚实的基础之上:如果您可以避免,则无法加载文件不应该是应用程序中的致命错误。捕获错误更合适。

无论你做什么来处理资源不足(包括让错误关闭应用程序),如果你关心后果,那么彻底测试它是非常重要的。困难在于,您永远不知道代码中的哪个点会出现问题,因此通常需要测试大量程序状态。


答案 2

理想情况下,重构代码以使用更少的内存。例如,也许您可以流式传输输出,而不是将整个内容保存在内存中。

或者,只需使用该选项为JVM提供更多内存即可。-Xmx