如何处理“java.lang.OutOfMemoryError: Java heap space”错误?

565

我正在使用Java 5编写客户端的Swing应用程序(图形字体设计器)。最近,由于我在内存使用上没有保守,因此遇到了java.lang.OutOfMemoryError: Java heap space 错误。用户可以打开无限数量的文件,而程序会将已打开的对象保存在内存中。经过快速研究,我发现在Windows机器上JVM默认的最大堆大小为64MB,详情请参阅Java虚拟机5.0中的人体工程学等文献。

鉴于这种情况,我应该如何处理这个限制呢?

我可以使用命令行选项最大堆大小增加到可用RAM,然后编写一些启动程序或脚本。但是,增加到一些有限的最大值并不能彻底解决问题。

我可以重写我的一些代码,经常将对象持久化到文件系统中(使用数据库也是同样的道理),以释放内存。这可能有效,但也需要付出很多努力。

如果您能指导我关于上述想法的详细信息或类似自动虚拟内存,动态扩展堆大小的替代方案,那就太好了。


默认的最大堆大小为64 MB,这是在J2SE 5.0之前的设置。有关J2SE 8.0信息,请参阅"http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gc-ergonomics.html"上的“垃圾收集器人体工程学”。 - Andy Thomas
5
如果您因为每个OOM问题都被重复到这个问题而来到这里,请确保您也查看以下内容:https://dev59.com/SnVC5IYBdhLWcg3wbQfA 它提供了一种在OOM之前“及时”清理内存引用的解决方案。SoftReferences可能是解决您实际问题的工具。 - Steve Steiner
Sun/Oracle JVM 一直非常严格地规定要使用多少内存(如果不规定,则会使用一些有趣的默认值)。那是微软 JVM 的其中一个好处 - 它非常快速,并且可以使用机器拥有的任何内存。 - Thorbjørn Ravn Andersen
在我的情况下,我错过了将 gradle.properties 文件添加到我的项目中。 - Mohd Qasim
31个回答

307

无论在哪个平台上运行,您始终有一个有限的堆最大使用量。在 Windows 32 位中,这大约是 2GB(不只是堆而是每个进程的总内存量)。恰巧 Java 选择使默认值更小(可能是为了防止程序员创建具有无限制内存分配的程序,而遇到此问题并不得不检查他们正在做什么)。

因此,鉴于这一点,您可以采取多种方法来确定所需的内存量或减少使用的内存量。使用类似于 Java 或 C# 的垃圾回收语言时常见的一个错误是保留对您不再使用的对象的引用,或者在可以重用它们时分配许多对象。只要对象有引用,它们将继续使用堆空间,因为垃圾回收器不会删除它们。

在这种情况下,您可以使用 Java 内存分析器确定程序中分配大量对象的方法,然后确定是否有方法确保它们不再被引用或者首次不要分配它们。我以前使用过的选项之一是“JMP”http://www.khelekore.org/jmp/

如果您确定正在分配这些对象是有原因的,并且您需要保留引用(根据您正在做的工作可能是这种情况),则只需在启动程序时增加最大堆大小即可。但是,一旦进行内存分析并了解如何分配对象,您应该更清楚所需的内存量。

通常情况下,如果无法保证程序在某个有限的内存范围内运行(可能取决于输入大小),那么您将始终遇到此问题。仅在尝试了所有这些解决方案后,您需要考虑将对象缓存到磁盘中等技术。此时,您应该有一个非常好的理由来说明“我需要XGB的内存”用于某些任务,并且您不能通过改善算法或内存分配模式来解决它。一般而言,这通常只适用于处理大型数据集(例如数据库或某些科学分析程序)的算法,此时诸如缓存和内存映射IO之类的技术会变得有用。


10
OpenJDK和OracleJDK都有捆绑的分析器-jvisualvm。如果你需要更多便利,我建议使用商业版的Yourkit。 - Petr Gladkikh
2
使用关闭未使用的对象和数组,例如resultsSet.close(); fileOutputStream.close(); fileOutputStream.flush(); 我使用了resultsSet.close(),它工作得很神奇。 - MrSalesi

155

使用命令行选项-Xmx运行Java,它可以设置堆的最大大小。

详见此处


6
如何永久设置此参数?因为我正在使用“gradlew assemble”命令。 - Dr.jacky
5
运行 -> 运行配置 -> 点击参数 -> 在 VM 参数中输入 -Xms1g -Xmx2g - Arayan Singh

104
你可以针对每个项目指定需要多少堆空间。Eclipse Helios/Juno/Kepler 中的操作如下:右击鼠标,然后选择
 Run As - Run Configuration - Arguments - Vm Arguments, 

然后添加这个

-Xmx2048m

1
嗨,bighostkim和cuongHuyTo,"Arguments"在哪里?我只能看到运行配置。请告诉我。我的需求是从Gmail下载并存储近2000个联系人。由于内存不足异常而崩溃了。 - AndroidRaji
@AndroiRaji:你需要右键单击鼠标选择具有可执行main函数(即“public static void main(String[] args)”)的Java类,然后选择“Run As - Run Configuration”。接下来,“Arguments”是在Main选项卡之后的一个选项卡(你将看到Main、Arguments、JRE、Classpath、Source、Environment、Common这些选项卡)。 - CuongHuyTo
intellih 会是什么样子? - Sal-laS

75

增加堆大小并不是一种“修复”,而是一种“临时措施”。它会在其他地方再次崩溃。为避免这些问题,请编写高性能代码。

  1. 尽可能使用局部变量。
  2. 确保选择正确的对象(例如:在String、StringBuffer和StringBuilder之间进行选择)
  3. 为程序使用一个良好的代码系统(例如:使用静态变量与非静态变量)
  4. 其他可能适用于您的代码的东西。
  5. 尝试采用多线程。

这是非常正确的。我正试图修复一个问题,即在AWT线程中遇到OOM,但如果我使用不同的新线程,则不会遇到OOM问题。我能在网上找到的就是增加AWT线程的堆大小。 - Ashish
2
@Ash:是的,修复核心问题,而不是寻找临时解决方案。 - PeakGen
垃圾回收和Java中的内存管理方法本应解决其前身malloc-dealloc的所有复杂性 :( 当然,我完全同意这个答案,只是遗憾的是默认设置并不容易编写具有精简数据结构且尽快清理的代码。 - Davos
8
我不同意。你需要最初将其设置为某个值。如果发现该值不足,这并不一定意味着你的应用程序有问题。也许你过于乐观了。在这种情况下,将其设置为更高的值是一个有效的解决方案。 - Zsolt Sky
有时候问题出在数据上,而不是代码上。确实存在着需要在内存中进行交叉检查的大规模数据集。 - undefined

33

需要注意的是——在我们的办公室里,我们发现(在某些 Windows 机器上)无法为 Java 堆分配超过512m的空间。这是由于安装在其中一些机器上的Kaspersky防病毒产品所致。卸载该 AV 产品后,我们发现可以至少分配1.6gb,即-Xmx1600m(否则将导致另一个错误“初始堆太小”)。

不知道其他 AV 产品是否也会出现类似问题,但想必这是因为 AV 程序在每个地址空间中保留了一小块内存,从而防止单个非常大的分配。


26

虚拟机参数对于我在eclipse中起作用。如果您使用的是eclipse 3.4版本,请执行以下操作:

转到 Run --> Run Configurations -->,然后选择maven构建下的项目 --> 选择"JRE"选项卡 --> 然后输入-Xmx1024m

或者你可以执行 Run --> Run Configurations --> 选择“JRE”选项卡--> 然后输入 -Xmx1024m

这将增加所有构建/项目的内存堆大小。上述内存大小为1 GB。您可以根据需要进行优化。


25

我想在文章中添加来自甲骨文 故障排除 文章的建议。

线程 thread_name 中的异常:java.lang.OutOfMemoryError: Java heap space

详细信息 Java heap space 表示无法在 Java 堆中分配对象。此错误并不一定意味着存在内存泄漏。

可能的原因:

  1. 简单的配置问题,指定的堆大小对应用程序来说不足。

  2. 应用程序无意中保留了对对象的引用,这会阻止对象被垃圾回收。

  3. 过多使用 finalizer

另一个潜在的错误来源是对使用 finalizer 过度的应用程序。如果一个类有一个 finalize 方法,则该类型的对象在垃圾回收时不会恢复其空间。

在进行垃圾回收后,对象会被排队等待“finalization”(终结),这个过程会在稍后发生。由一个守护线程执行“finalizers”(终结器),该线程服务于终结队列。如果“finalizer”线程无法跟上终结队列的速度,则Java堆可能会填满,从而引发此类“OutOfMemoryError”异常。
导致这种情况的一种场景是,当应用程序创建了“high-priority threads”(高优先级线程)时,会导致“finalization”队列的增长速度快于“finalizer”线程服务该队列的速度。

20

使用-Xmx参数可以为您的JVM配置更多的内存。为了确保您没有泄漏或浪费内存,可以进行堆转储,并使用Eclipse Memory Analyzer来分析您的内存消耗。


3
JVMJ9VM007E命令行选项无法识别:-Xmx。无法创建Java虚拟机。 - Philip Rego

12

请按以下步骤操作:

  1. 打开tomcat/bin目录下的catalina.sh文件。

  2. 将JAVA_OPTS更改为

  3. JAVA_OPTS="-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms1536m 
    -Xmx1536m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=256m 
    -XX:MaxPermSize=256m -XX:+DisableExplicitGC"
    
  4. 重新启动您的Tomcat


10

默认情况下,开发JVM使用小的大小和小的配置进行其他性能相关功能。但是对于生产环境,您可以进行调整,例如(另外,应用程序服务器特定配置可能存在)->(如果仍然没有足够的内存满足请求,并且堆已经达到最大大小,则会发生OutOfMemoryError)

-Xms<size>        set initial Java heap size
-Xmx<size>        set maximum Java heap size
-Xss<size>        set java thread stack size

-XX:ParallelGCThreads=8
-XX:+CMSClassUnloadingEnabled
-XX:InitiatingHeapOccupancyPercent=70
-XX:+UnlockDiagnosticVMOptions
-XX:+UseConcMarkSweepGC
-Xms512m
-Xmx8192m
-XX:MaxPermSize=256m (in java 8 optional)
例如:在Linux平台上,生产模式下的首选设置。按照以下方式下载和配置服务器:http://www.ehowstuff.com/how-to-install-and-setup-apache-tomcat-8-on-centos-7-1-rhel-7/。在/opt/tomcat/bin/文件夹中创建setenv.sh文件。
   touch /opt/tomcat/bin/setenv.sh

2.打开并写入以下参数以设置首选模式。

nano  /opt/tomcat/bin/setenv.sh 

export CATALINA_OPTS="$CATALINA_OPTS -XX:ParallelGCThreads=8"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+CMSClassUnloadingEnabled"
export CATALINA_OPTS="$CATALINA_OPTS -XX:InitiatingHeapOccupancyPercent=70"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UnlockDiagnosticVMOptions"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseConcMarkSweepGC"
export CATALINA_OPTS="$CATALINA_OPTS -Xms512m"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx8192m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxMetaspaceSize=256M"

3.service tomcat restart

请注意,JVM使用的内存不仅限于堆内存。例如,Java方法、线程栈和本地句柄分配在与堆内存分离的内存中,同时,JVM内部数据结构也分配在其他内存中。


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