堆空间达到最大值之前出现OutOfMemoryError错误?

3
我遇到了一个非常奇怪的问题,我想在SSCCE中重现它,但我不能。

我正在Java8(32位)中运行程序,并使用-Xmx1024m将一个相当大的文件(120MB)加载到字节数组中使用FileInputStream

问题是,在Java6中我没有任何问题,但在Java8中,当我尝试加载它时,我会立即收到OutOfMemoryError异常。

我仍然有很多可用内存,并且我已经对其进行了分析,没有发现问题。

如果我尝试从SSCCE中提取出这个问题,则可以正常工作。

我知道Oracle已经摆脱了PermGen,但这可能会影响我的程序吗?

我也读到说这可能与堆空间的碎片化问题有关,但我尝试过调试、分析,并在分配内存之前从分析器中运行GC周期,仍然发生这种情况(我认为GC周期会整理堆空间)。


首先,检查 OOME 的消息,它不一定是堆空间限制。其次,使用分析器,查看哪些内容占用了更多的内存。 - the8472
不包含任何“null”信息 - lqbweb
使用相同的 -Xmx1024m 参数,但在 64 位系统上运行可以正常工作。 - lqbweb
当我激活-verbose:gc时,我看到: [GC(分配失败)[DefNew:216334K-> 4995K(314560K),0.0130429秒] 264354K-> 53015K(1013632K),0.0130844秒] [时间:用户= 0.02 sys = 0.00,real = 0.01秒] 再次,为什么它不进入老年代池? - lqbweb
3个回答

4

在Java8中,一些之前属于PermGenSpace的内容被移到堆内存中,例如字符串池。还要检查分配给老年代的大小。


在VisualGC中,老年代池仍然有600MB的空闲空间。 - lqbweb
如果您遇到了“尝试创建一个对象,该对象首先在年轻代中创建,而年轻代对其来说太小”的问题,则可能需要调整年轻代的大小。 - jmj

2
我想我已经找到了原因。根据这个错误报告:

https://bugs.openjdk.java.net/browse/JDK-6478546

FileInputStream 直接将空间保留到本地内存中。当增加最大堆空间的大小时,实际上本地映射的空间更少,情况会变得更糟。

我的代码之所以能在Java8 64位上运行,正是因为我有更多来自Windows的虚拟空间,而不受Windows约束在~1.7Gb左右。

我仍然不太清楚为什么相同的代码在Java6中可以工作。似乎相同的应用程序在Java8中使用了比Java6更多的本地空间。也许采用Metaspace的新方案稍微改变了一些东西。我不确定。

有关此问题的有趣文章:

http://www.codingthearchitecture.com/2008/01/14/jvm_lies_the_outofmemory_myth.html


0

有几件事情。

  • 1 GB 并不多,即使在 Windows 32 位系统上,您也可以将其设置为 1400 MB。
  • Java 8 的工作方式与 Java 6 不同,并且它是针对内存比 9 年前 Java 6 发布时更便宜的假设进行优化的。
  • 您可以在 120 MB 中进行内存映射,并且完全不使用堆。这可能是一种更有效的方法。
  • 所消耗的内存几乎肯定是您处理数据的大小,而不是文件本身的大小。我不会假设您实际上只使用了 120 MB 来处理此文件。

我建议您使用充足的内存对应用程序进行内存分析,如果必须使用 64 位 JVM,则了解内存的消耗情况。


谢谢。我已经按照您的建议使用64位的2GB来对应用程序进行分析,执行该操作时从Eden空间中使用了约270MB,在32位系统中,最大的Eden空间大约为250MB,但tenured池仍有约600MB的可用空间。 我理解您关于将该分配移动到本地内存的建议,但这段代码不在我的控制范围之内(它是一个库)。 我只想了解其中的原因。 再次感谢! - lqbweb
@Ruben,你可以在 OOME 出现的时候触发 JVM 来进行堆转储,这样可以更清楚地了解此时使用了哪些空间。 - Peter Lawrey

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