我已将最大堆设置为8 GB。当我的程序开始使用大约6.4 GB(在VisualVM中报告)时,垃圾收集器开始占用大部分CPU,并且在进行~100 MB分配时程序与OutOfMemory崩溃。我在Windows上使用Oracle Java 1.7.0_21。
我的问题是,是否有GC选项可以帮助解决这个问题。除了-Xmx8g,我没有传递任何东西。
我的猜测是堆已经碎片化了,但GC不应该紧凑吗?
答案 0 :(得分:3)
收集一些信息(由于official documentation非常糟糕,这是非常困难的),我已经确定......
这可能发生两种原因,两者都与自由空间的碎片有关(即,小块中存在的自由空间使得无法分配大对象)。首先,垃圾收集器可能不会进行压缩,也就是说它不会对内存进行碎片整理。即使是压实的收集器也可能不能很好地完成。其次,垃圾收集器通常将内存区域划分为它为不同类型的对象保留的区域,并且它可能不会考虑从具有它的区域获取空闲内存来提供给需要它的区域。
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
中在Java中使用它。
答案 1 :(得分:2)
您使用的是哪个垃圾收集器? CMS不做任何压缩。尝试使用新的G1 garbage collector - 这会做一些压缩。
对于一些上下文:G1垃圾收集器或“垃圾优先”收集器将堆分成块并在识别(标记)所有垃圾后,它将通过复制所有撤离块将比特分成不同的块 - 这就是实现压缩的原因。
使用包含选项-XX:+UseG1GC
This给出了Java中G1和垃圾收集的一个很好的解释。
答案 2 :(得分:2)
每当这个问题出现在过去时,实际的可用内存远低于它出现的内存。发生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;
}
答案 3 :(得分:0)
最有可能的是,你正在尝试分配大量的连续内存,但是所有的空闲内存都是一点点的。此外,当垃圾收集器开始占用所有处理时间时,这意味着它当前正在尝试在可以释放的整个对象集中找到可能的1或2个对象。在这种情况下,我认为你可以做的就是将你的对象打破,这样他们就不需要那么多的连续内存(至少,不是所有的一次)。
编辑:据我所知,你无法让Java打包内存,以便你可以使用那个完整的8 GB,因为它会让垃圾收集器不得不暂停所有其他线程,移动他们的对象,更新对这些对象的所有引用,然后刷新陈旧的缓存条目,等等......这是一个非常非常昂贵的操作。
答案 4 :(得分:0)
-Xmx只能确保堆的大小不超过8GB,但不能保证实际分配这么多的内存。你的机器只有8GB的内存吗?