我已经确定了点点滴滴的信息(这很困难,因为官方文档非常糟糕),我已经确定…
通常可能有两个原因,这两个原因都与自由空间的碎片化(即,存在于小块中的自由空间使得无法分配大对象)有关。首先,垃圾收集器可能不会进行压缩,也就是说不会对内存进行碎片整理。即使是执行压缩的收集器也可能无法很好地完成它。其次,垃圾收集器通常将内存区域划分为不同类型的对象保留的区域,并且它可能不会考虑从拥有内存的区域中获取可用内存以提供给需要内存的区域。
CMS垃圾收集器不进行压缩,而其他垃圾收集器(串行,并行,parallelold和G1)则进行压缩。Java 8中的默认收集器是ParallelOld。
所有垃圾收集器都将内存划分为多个区域,而且AFAIK都非常懒惰,因此很难尝试防止OOM错误。命令行选项
-XX:+PrintGCDetails对于某些收集器在显示区域的大小以及它们具有多少可用空间方面非常有用。
可以尝试使用不同的垃圾收集器和调整选项。关于我的问题,
G1收集器(启用了JVM标志
-XX:+UseG1GC)解决了我遇到的问题。但是,这基本上是偶然的(在其他情况下,OOM更快)。一些收集器(串行收集器,cms和G1)具有广泛的调整选项,可以选择各个区域的大小,从而使您浪费时间徒劳地尝试解决问题。
最终,真正的解决方案令人不快。首先,是安装更多的RAM。其次,是使用较小的数组。第三,就是使用
ByteBuffer.allocateDirect。直接字节缓冲区(及其int
/ float /
double包装器)是具有类似数组性能的类似于数组的对象,它们分配在OS的本机堆上。操作系统堆使用CPU的虚拟内存硬件,没有碎片问题,甚至可以有效地使用磁盘的交换空间(允许您分配比可用RAM更多的内存)。但是,一个很大的缺点是,JVM并不真正知道何时应该释放直接缓冲区,这使得该选项更适合长寿命的对象。最后,可能是最好的,当然也是最不愉快的选择是分配
和取消分配 使用JNI调用本机存储,并通过将其包装在Java中在Java中使用它
ByteBuffer。



