如何在 Java 中处理 OutOfMemoryError? [复制]

Posted

技术标签:

【中文标题】如何在 Java 中处理 OutOfMemoryError? [复制]【英文标题】:How to handle OutOfMemoryError in Java? [duplicate] 【发布时间】:2010-10-05 09:22:54 【问题描述】:

我必须序列化大约一百万个项目,并且在运行代码时出现以下异常:

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)

我该如何处理?

【问题讨论】:

不建议处理“OutOfMemoryError”。您能否向我们提供一些细节,比如对象在哪里,以及序列化数据需要去哪里?有一些方法可以根据您的回答限制内存使用量。 【参考方案1】:

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

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

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

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

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

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

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

如果错误发生在反序列化过程中,那么您的立场就更坚定了:如果您可以避免,加载文件失败不应该是应用程序中的致命错误。捕获错误可能更合适。

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

【讨论】:

一旦发生OOM,应用程序必须尽快关闭,因为它处于不稳定状态,这是真的吗?还是我可以简单地抓住它并继续? @Pacerier:不一定是真的,但这取决于异常来自哪里。如果您写try new int[100*1024*1024]; catch (OutOfMemoryError);,那么您没有理由不能继续。但是如果异常来自某个库,您不知道该库是否已处于稳定状态,因为如果编写它的程序员共享 OOM 不可恢复的观点,他们可能没有做出任何努力以确保代码在尝试分配内存时处于可恢复状态。【参考方案2】:

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

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

【讨论】:

只是想补充一点,默认的最大堆大小为 64MB(在 32 位系统上)。很可能您只需要更多。如果你增加它(尝试加倍)并且仍然得到 OOM,然后考虑让你的代码更有效率。 在 32jvm 上,我相信 Sun 的 JVM 可以处理的最大内存约为 1.8GB。我相信您可以在 64 位 JVM 上走得更高,而众所周知,其他供应商的 JVM 会将这个限制推得更高。只是一个警告 请记住,有几种不同的 OutOfMemory 错误,只有一些可以使用 -Xmx 修复。例如,如果你的代码试图分配一个元素超过 2G 的数组,你会得到一个 OOM,没有成员你分配了多少内存。我已经在 Tomcat 下运行的 servlet 中的 ByteOutputStream 中看到了这种情况,该 servlet 试图序列化具有 HUGE sessionstate 的会话。在这种情况下,您可能想要捕获并处理 OutOfMemory 错误。【参考方案3】:

您不应该在代码中处理它。 OutOfMemory 不应被捕获和处理。而是使用更大的堆空间启动 JVM

java -Xmx512M

应该可以解决问题。

详情请见here

【讨论】:

网址无效【参考方案4】:

其他人已经介绍了如何为 Java 提供更多内存,但是因为“句柄”可能意味着捕获,所以我将引用 Sun 对Errors 所说的话:

ErrorThrowable 的子类 这表明严重的问题 合理的应用不应该尝试 赶上。大多数此类错误是 异常情况。

(强调我的)

【讨论】:

哪里不应该不代表不能! +1 我有几个案例可以处理 UnsatisfiedLinkError ThreadDeath 是另一个你可能想要捕获的线程,如果你需要对所述线程进行一些清理。【参考方案5】:

您收到 OutOfMemoryError 是因为您的程序需要的内存超出了 JVM 可用的内存。您无法在运行时专门做任何事情来帮助解决此问题。

正如 krosenvold 所指出的,您的应用程序可能对内存提出了合理的要求,但碰巧 JVM 没有启动足够的内存(例如,您的应用程序将有 280MB 的峰值内存占用,但 JVM 仅以 256MB 开始) .在这种情况下,增加分配的大小将解决这个问题。

如果您觉得在启动时提供了足够的内存,那么您的应用程序可能暂时使用了过多的内存,或者存在内存泄漏。在您发布的情况下,听起来您一次持有对内存中所有数百万个项目的引用,即使您可能是按顺序处理它们。

检查您对“完成”项目的引用是什么样的 - 您应该尽快尊重这些,以便它们可以被垃圾收集。例如,如果您将一百万个项目添加到集合中,然后迭代该集合,您将需要足够的内存来存储所有这些对象实例。看看你是否可以一次取一个对象,序列化它然后丢弃引用。

如果您在解决这个问题时遇到问题,发布伪代码 sn-p 会有所帮助。

【讨论】:

【参考方案6】:

除了已经给你的一些提示,如复习记忆力不足和 还启动具有更多内存的 JVM (-Xmx512M)。 看起来你有一个 OutOfMemoryError 导致你的 TopicParser 正在读取一个可能相当大的行(这是你应该避免的),你可以使用 FileReader (或者,如果编码是一个问题,一个 InputStreamReader 包装一个 FileInputStream)。将其 read(char[]) 方法与 合理 大小的 char[] 数组用作缓冲区。

最后还要调查一下为什么可以使用 OutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError 在 JVM 中标记以获取转储堆信息到磁盘。

祝你好运!

【讨论】:

【参考方案7】:

有趣 - 您在读取行上出现内存不足。猜测一下,您正在读取一个没有换行符的大文件。

与其使用 readline 将文件中的内容作为一个大长字符串从文件中取出,不如编写能够更好地理解输入的内容,并以块的形式处理。

如果您只是必须将整个文件放在一个大长字符串中......好吧,在编码方面做得更好。一般来说,试图通过将数据全部填充到单个字节数组(或其他)中来处理数千兆字节数据是一种很好的失败方式。

去看看 CharacterSequence。

【讨论】:

【参考方案8】:

使用transient关键字标记序列化类中可以从现有数据生成的字段。 实现 writeObject 和 readObject 以帮助重建瞬态数据。

【讨论】:

【参考方案9】:

按照增加堆空间的建议(通过 -Xmx)但一定要使用JConsole 或JVisualVM 来分析您的应用程序内存使用情况。确保内存使用量不会持续增长。如果是这样,您仍然会收到 OutOfMemoryException,只是需要更长的时间。

【讨论】:

【参考方案10】:

为选项 -Xmx 设置一个较大的值,例如 -Xmx512m

【讨论】:

【参考方案11】:

没有真正的方法可以很好地处理它。一旦它发生,你就在未知的领域。您可以通过名称来判断 - OutOfMemoryError。它被描述为:

当 Java 虚拟机无法分配对象,因为它超出了 内存,并且垃圾无法提供更多内存 收集器

通常 OutOfMemoryError 表示系统/方法存在严重错误(很难指出触发它的特定操作)。

这通常与堆空间不足有关。使用 -verbosegc 和前面提到的 -XX:+HeapDumpOnOutOfMemoryError 应该会有所帮助。

您可以在javaperformancetuning 找到一个简洁明了的问题摘要

【讨论】:

【参考方案12】:

在采取任何危险、耗时或战略性的行动之前,您应该准确确定程序中的哪些内容正在占用如此多的内存。你可能认为你知道答案,但在你有证据之前,你不知道。内存有可能被你没想到的东西占用了。

使用分析器。不管是哪一个,there are plenty of them。首先找出每个对象使用了多少内存。其次,逐步完成序列化程序的迭代,比较内存快照并查看创建了哪些对象或数据。

答案很可能是流式传输输出,而不是在内存中构建它。但首先要获得证据。

【讨论】:

【参考方案13】:

您可以使用 -Xmx 选项增加 java 使用的内存大小,例如:

java -Xmx512M -jar myapp.jar

更好的是减少应用程序的内存占用。你序列化数以百万计的项目?您需要将所有这些都保存在内存中吗?或者你可以在使用它们之后释放它们中的一些吗?尽量减少使用的对象。

【讨论】:

【参考方案14】:

我发现了一个替代方案,尊重所有其他观点,我们不应该试图从异常中捕获内存,这是我最近学到的。

catch (Throwable ex)
 if (!(ex instanceof ThreadDeath))
 
  ex.printStackTrace(System.err);
 

供您参考:OutOfMemoryError 欢迎任何反馈。

阿维舍克阿朗

【讨论】:

以上是关于如何在 Java 中处理 OutOfMemoryError? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

[编织消息框架][JAVA核心技术]异常基础

OutOfMemoryError 发生:播放框架中的 Java 堆空间

java面试-强引用软引用弱引用和幻象引用有什么区别

Ucanaccess JDBC 驱动程序 - 内存=false 设置的内存不足错误

java.lang.OutOfMemoryError: Failed to allocate a 3110419 byte allocation with 741152 free bytes and(

如何在 Java 中处理同时按键?