为什么当20%的堆仍然空闲时,我会获得OutOfMemory? [英] Why do I get OutOfMemory when 20% of the heap is still free?

查看:92
本文介绍了为什么当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。其次,是使用更小的阵列。第三,使用 ByteBuffer.allocateDirect 。直接字节缓冲区(以及它们的int / float / double wrappers)是类似数组的对象,具有类似数组的性能,这些对象分配在操作系统的本机堆上。操作系统堆使用CPU的虚拟内存硬件并且没有碎片问题,甚至可以有效地使用磁盘的交换空间(允许您分配比可用内存更多的内存)。然而,一个很大的缺点是JVM并不知道何时应该释放直接缓冲区,这使得这个选项对于长寿命的对象来说更可取。最后的,可能是最好的,当然也是最不愉快的选择是使用JNI调用本地分配内存和释放内存,并通过将它用Java包装到 ByteBuffer


I've set the max heap to 8 GB. When my program starts using about 6.4 GB (as reported in VisualVM), the garbage collector starts taking up most of the CPU and the program crashes with OutOfMemory when making a ~100 MB allocation. I am using Oracle Java 1.7.0_21 on Windows.

My question is whether there are GC options that would help with this. I'm not passing anything except -Xmx8g.

My guess is the heap is getting fragmented, but shouldn't the GC compact it?

解决方案

Collecting bits and pieces of information (which is surprisingly difficult, since the official documentation is quite bad), I've determined...

There are generally two reasons this may happen, both related to fragmentation of free space (ie, free space existing in small pieces such that a large object cannot be allocated). First, the garbage collector might not do compaction, which is to say it does not defragment the memory. Even a collector that does compaction may not do it perfectly well. Second, the garbage collector typically splits the memory area into regions that it reserves for different kinds of objects, and it may not think to take free memory from the region that has it to give to the region that needs it.

The CMS garbage collector does not do compaction, while the others (the serial, parallel, parallelold, and G1) do. The default collector in Java 8 is ParallelOld.

All garbage collectors split memory into regions, and, AFAIK, all of them are too lazy to try very hard to prevent an OOM error. The command line option -XX:+PrintGCDetails is very helpful for some of the collectors in showing the sizes of the regions and how much free space they have.

It is possible to experiment with different garbage collectors and tuning options. Regarding my question, the G1 collector (enabled with the JVM flag -XX:+UseG1GC) solved the issue I was having. However, this was basically down to chance (in other situations, it OOMs more quickly). Some of the collectors (the serial, cms, and G1) have extensive tuning options for selecting the sizes of the various regions, to enable you to waste time in futilely trying to solve the problem.

Ultimately, the real solutions are rather unpleasant. First, is to install more RAM. Second, is to use smaller arrays. Third, is to use ByteBuffer.allocateDirect. Direct byte buffers (and their int/float/double wrappers) are array-like objects with array-like performance that are allocated on the OS's native heap. The OS heap uses the CPU's virtual memory hardware and is free from fragmentation issues and can even effectively use the disk's swap space (allowing you to allocate more memory than available RAM). A big drawback, however, is that the JVM doesn't really know when direct buffers should be deallocated, making this option more desirable for long-lived objects. The final, possibly best, and certainly most unpleasant option is to allocate and deallocate memory natively using JNI calls, and use it in Java by wrapping it in a ByteBuffer.

这篇关于为什么当20%的堆仍然空闲时,我会获得OutOfMemory?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆