我已将最大堆设置为 8 GB。当我的程序开始使用大约 6.4 GB(如 VisualVM 中所报告的)时,垃圾收集器开始占用大部分 CPU,并且在分配 ~100 MB 时程序因 OutOfMemory 崩溃。我在 Windows 上使用 Oracle Java 1.7.0_21。
我的问题是是否有 GC 选项可以帮助解决这个问题。除了-Xmx8g,我什么都没通过。
我的猜测是堆越来越碎片化,但 GC 不应该压缩它吗?
我已将最大堆设置为 8 GB。当我的程序开始使用大约 6.4 GB(如 VisualVM 中所报告的)时,垃圾收集器开始占用大部分 CPU,并且在分配 ~100 MB 时程序因 OutOfMemory 崩溃。我在 Windows 上使用 Oracle Java 1.7.0_21。
我的问题是是否有 GC 选项可以帮助解决这个问题。除了-Xmx8g,我什么都没通过。
我的猜测是堆越来越碎片化,但 GC 不应该压缩它吗?
收集点点滴滴的信息(这非常困难,因为官方文档很糟糕),我已经确定......
发生这种情况的原因通常有两个,都与空闲空间的碎片有关(即,空闲空间以小块的形式存在,无法分配大对象)。首先,垃圾收集器可能不会进行压缩,也就是说它不会对内存进行碎片整理。即使是进行压缩的收集器也可能无法做到完美。其次,垃圾收集器通常将内存区域划分为它为不同类型的对象保留的区域,并且它可能不会考虑从拥有它的区域中获取空闲内存来提供给需要它的区域。
CMS 垃圾收集器不进行压缩,而其他垃圾收集器(串行、并行、parallelold 和 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
.
每当这个问题在过去出现时,实际的可用内存就会比它出现的要低得多。当发生 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;
}
Most likely, you are trying to allocate a large amount of contiguous memory, but all of the free memory is in little bits and pieces all over the place. Also, when the garbage collector starts taking up all of the processing time, that means that it is currently in the process of trying to find the maybe 1 or 2 objects in your whole set of objects that can be freed. In this case, all I think you can do is work on breaking your objects down so that they do not need quite as much continuous memory (at least, not all at one time).
Edit: As far as I know, there is no way that you can get Java to pack the memory so that you can use that full 8 GB, as it would involve the Garbage Collector having to pause all of the other threads, moving their objects around, updating all of the references to those objects, then refreshing stale cache entries, and so on...a very, very expensive operation.
See this about Memory Fragmentation
-Xmx 仅确保堆大小不会超过 8GB,但不保证实际分配这么多内存。你的机器只有8GB内存吗?