当20%的堆仍然是免费的时,为什么我会得到OutOfMemory?

我已将最大堆设置为8 GB。 当我的程序开始使用大约6.4 GB(在VisualVM中报告)时,垃圾收集器开始占用大部分CPU,并且在进行~100 MB分配时程序与OutOfMemory崩溃。 我在Windows上使用Oracle Java 1.7.0_21。

我的问题是,是否有GC选项可以帮助解决这个问题。 除了-Xmx8g,我没有传递任何东西。

我的猜测是堆已经碎片化了,但GC应该不紧凑吗?

收集一些信息(这非常困难,因为官方文件非常糟糕),我已经确定……

这可能发生两种原因,两者都与自由空间的碎片有关(即,小块中存在的自由空间使得无法分配大对象)。 首先,垃圾收集器可能不会进行压缩,也就是说它不会对内存进行碎片整理。 即使是压实的收集器也可能不能很好地完成。 其次,垃圾收集器通常将内存区域拆分为为不同类型的对象保留的区域,并且可能不会考虑从具有它的区域获取空闲内存以提供给需要它的区域。

CMS垃圾收集器不进行压缩,而其他(串行,并行,并行和G1)则执行压缩。 Java 8中的默认收集器是ParallelOld。

所有的垃圾收集器都将内存分成了区域,而且,AFAIK,所有这些都太懒了,不能很努力地防止出现OOM错误。 命令行选项-XX:+PrintGCDetails对于某些收集器显示区域的大小以及它们具有多少可用空间非常有用。

可以尝试不同的垃圾收集器和调整选项。 关于我的问题, G1收集器(使用JVM标志-XX:+UseG1GC )解决了我遇到的问题。 然而,这基本上是机会(在其他情况下,OOM更快)。 一些收集器(串行,cms和G1)具有广泛的调整选项,用于选择不同区域的大小,使您可以徒劳地浪费时间尝试解决问题。

最终,真正的解决方案是相当不愉快的。 首先,是安装更多的RAM。 第二,是使用较小的arrays。 第三,是使用ByteBuffer.allocateDirect 。 直接字节缓冲区(及其int / float / double包装器)是类似于数组的对象,具有类似于数组的性能,这些对象在操作系统的本机堆上分配。 操作系统堆使用CPU的虚拟内存硬件,没有碎片问题,甚至可以有效地使用磁盘的交换空间(允许您分配比可用RAM更多的内存)。 然而,一个很大的缺点是JVM并不真正知道何时应该释放直接缓冲区,这使得该选项对于长期存在的对象更为理想。 最后的,可能是最好的,当然也是最令人不愉快的选择是使用JNI调用本地分配和释放内存,并通过将其包装在ByteBuffer来在Java中使用它。

你在使用哪个垃圾收集器? CMS不做任何压缩。 尝试使用新的G1垃圾收集器 – 这会做一些压缩。

对于一些上下文:G1垃圾收集器或“垃圾优先”收集器将堆分成块并在识别(标记)所有垃圾后,它将通过将所有实时位复制到不同的块中来撤出块 – 这是什么实现了压实。

要使用包含选项-XX:+UseG1GC

这给出了Java中G1和垃圾收集的一般解释。

每当这个问题在过去出现时,实际的可用内存远低于它出现的内存。 发生OutOfMemoryError时,可以打印可用内存量。

 try { byte[] array = new byte[largeMemorySize]; } catch(OutOfMemroyError e) { System.out.printf("Failed to allocate %,d bytes, free memory= %,d%n", largeMemorySize, Runtime.getRuntime().freeMemory()); throw e; } 

最有可能的是,你正在尝试分配大量的连续内存,但是所有的可用内存都是一点点的。 此外,当垃圾收集器开始占用所有处理时间时,这意味着它当前正在尝试在可以释放的整个对象集中找到可能的1或2个对象。 在这种情况下,我认为你可以做的就是打破你的对象,这样他们就不需要那么多的连续内存(至少,不是所有的一次)。

编辑:据我所知,你无法让Java打包内存,以便你可以使用那个完整的8 GB,因为它会让垃圾收集器不得不暂停所有其他线程,移动它们的对象周围,​​更新对这些对象的所有引用,然后刷新陈旧的缓存条目,等等……一个非常非常昂贵的操作。

请参阅有关内存碎片的内容

-Xmx仅确保堆大小不超过8GB,但不保证实际分配这么多内存。 你的机器只有8GB的内存吗?