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

Ale*_*sky 17 java garbage-collection jvm-hotspot

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

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

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

Ale*_*sky 5

收集点点滴滴的信息(这非常困难,因为官方文档非常糟糕),我决定......

发生这种情况通常有两个原因,均与空闲空间的碎片化有关(即,空闲空间以小块形式存在,从而无法分配大对象)。首先,垃圾收集器可能不会进行压缩,也就是说它不会对内存进行碎片整理。即使是进行压缩的收集器也可能无法完美地完成。其次,垃圾收集器通常将内存区域划分为它为不同类型的对象保留的区域,并且它可能不会考虑从拥有它的区域中获取空闲内存分配给需要它的区域。

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

所有垃圾收集器都将内存拆分为多个区域,而且,AFAIK,所有垃圾收集器都太懒了,无法非常努力地防止 OOM 错误。命令行选项-XX:+PrintGCDetails对于一些收集器显示区域的大小和它们有多少可用空间非常有帮助。

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

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