为什么在 20% 的堆仍然可用时,我会得到 OutOfMemory?

2022-09-02 22:29:42

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

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

我的猜测是堆越来越碎片化,但GC不应该压缩它吗?


答案 1

收集零碎的信息(这非常困难,因为官方文档非常糟糕),我已经确定......

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

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

所有垃圾回收器都将内存拆分为多个区域,并且AFAIK,它们都懒得非常努力地防止OOM错误。命令行选项对于某些收集器在显示区域大小及其有多少可用空间方面非常有用。-XX:+PrintGCDetails

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

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


答案 2

您使用的是哪种垃圾回收器?CMS不做任何压缩。尝试使用新的 G1 垃圾回收器 - 这会进行一些压缩。

对于一些上下文:G1垃圾回收器或“垃圾优先”收集器将堆拆分为块,在识别(标记)所有垃圾后,它将通过将所有活动位复制到不同的块中来疏散块 - 这就是实现压缩的原因。

要使用,请包括以下选项-XX:+UseG1GC

为Java中的G1和垃圾回收提供了很好的解释。