如何在Java中处理OutOfMemoryError?

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

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOfRange(Unknown Source) at java.lang.String.(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) 

我该如何处理?

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

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

我知道Java的官方答案是“哦不!出于回忆!我放弃了!”。 对于那些在不允许内存不足(例如,编写操作系统或为非受保护的操作系统编写应用程序)的环境中进行编程的人来说,这一点非常令人沮丧。

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

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

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

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

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

这比尝试捕获错误并继续更强大,但不幸的是,很难找出确切的界限,所以你可能不得不谨慎行事。

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

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

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

 java -Xmx512M 

应该做的伎俩。

有关详细信息,请参见此处

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

ErrorThrowable的子类,表示合理的应用程序不应该尝试捕获的严重问题。 大多数此类错误都是exception情况。

(强调我的)

您得到一个OutOfMemoryError,因为您的程序需要的内存比JVM可用的内存多。 在运行时没有什么可以专门做的来帮助它。

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

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

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

如果您在解决此问题时遇到问题,发布伪代码段会有所帮助。

除了提供给你的一些技巧之外,还有内存所缺少的内容,并且还启动了具有更多内存的JVM(-Xmx512M)。 看起来你有一个OutOfMemoryError导致你的TopicParser读取的行可能很大(这是你应该避免的),你可以使用FileReader (或者,如果编码有问题,可以使用包装FileInputStream的InputStreamReader )。 使用其read(char [])方法和一个合理大小的char []数组作为缓冲区。

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

祝你好运!

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

有趣的是 – 你在readline上的内存不足。 猜测,你正在阅读没有换行符的大文件。

而不是使用readline将文件中的东西作为一个单独的大字符串来获取,编写能够更好地理解输入的内容,并以块的forms处理它。

如果你只需要将整个文件放在一个大的长字符串中……那么,在编码方面要做得更好。 一般来说,尝试通过将所有数据填充到一个字节(或其他)的单个数组来处理mutimegabyte数据是一种很好的方法。

去看看CharacterSequence。

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

选项-Xmx以较大的值启动java,例如-Xmx512m

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

当Java虚拟机因为内存不足而无法分配对象时抛出,并且垃圾收集器不再提供更多内存

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

通常它与普通的堆空间耗尽有关。 使用-verbosegc和前面提到的-XX:+ HeapDumpOnOutOfMemoryError应该有所帮助。

您可以在javaperformancetuning中找到问题的简洁摘要

在采取任何危险,耗时或战略性的行动之前,您应该准确确定程序中正在消耗这么多内存的内容。 你可能认为你知道答案,但在你面前有证据之前,你不会。 存储器可能被你没想到的东西使用。

使用分析器。 哪一个都没关系, 有很多 。 首先找出每个对象正在消耗多少内存。 其次,通过迭代序列化程序,比较内存快照并查看创建的对象或数据。

答案很可能是流输出而不是在内存中构建输出。 但首先得到证据。

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

 java -Xmx512M -jar myapp.jar 

更好的是减少应用程序的内存占用。 你序列化了数百万件物品? 你需要把所有这些都留在记忆中吗? 或者你可以在使用后释放其中一些吗? 尝试减少使用过的对象。

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

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

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

Avishek Arang