Java 为什么我得到OutOfMemoryError,但堆转储显示大量内存是可用的

Java 为什么我得到OutOfMemoryError,但堆转储显示大量内存是可用的,java,visualvm,eclipse-memory-analyzer,Java,Visualvm,Eclipse Memory Analyzer,我的Java程序从流中读取数据,并为其中的一部分创建内存缓存。在某个时候,它抛出了一个OutOfMemoryError,我已经让它在那个时候创建了一个堆转储,这样我就可以看到是什么导致了这个问题。 但是当我加载堆转储时,我看到大约一半的内存没有使用:我用-Xmx8000m启动了VM,当加载到Eclipse memory Analyzer或VirtualVM中时,堆转储只显示大约4GB的使用量。但是,转储文件本身的文件大小约为8GB 同样奇怪的是,这两个工具都将大量int[262136]大小的in

我的Java程序从流中读取数据,并为其中的一部分创建内存缓存。在某个时候,它抛出了一个OutOfMemoryError,我已经让它在那个时候创建了一个堆转储,这样我就可以看到是什么导致了这个问题。 但是当我加载堆转储时,我看到大约一半的内存没有使用:我用-Xmx8000m启动了VM,当加载到Eclipse memory Analyzer或VirtualVM中时,堆转储只显示大约4GB的使用量。但是,转储文件本身的文件大小约为8GB

同样奇怪的是,这两个工具都将大量int[262136]大小的int数组报告为“未引用对象”,即垃圾。其中大约有4GB——这确实表明它们不是垃圾,而是OOM的原因。。 顺便说一句,我的代码根本不会在这种大小的数组中创建

为什么我会得到这个OOM,那些int[]数组是怎么回事


我在Java 11 JDK上运行,但Java 14上也出现了同样的问题。

这是Java垃圾收集器的问题,很难找到

这些版本的默认垃圾收集器是G1收集器。此垃圾收集器将可用内存划分为固定大小的内存区域。从1MB开始,它们总是2的幂,这取决于-Xmx max内存参数

这些int[262136]数组是gc用来以某种方式将这些区域标记为Java对象的技巧。这个整数数组正好占用1mb的空间,因此它具有区域的大小。它将它们标记为未引用,因此大多数工具看不到它们,或者将它们标记为垃圾。这是高度误导,因为它似乎是OOM问题的原因

OOM的真正原因是缓存代码分配(并释放)被G1垃圾收集器视为“庞大对象”的对象。它在回收或移动这些对象方面存在巨大的问题,这显然会导致内存碎片——这反过来会导致OOM,即使看起来有足够的内存可用。出于某种原因,gc日志记录没有给出任何迹象表明这可能是一个问题8-(

要确定这是否是问题的原因,一个好的测试是使用旧的“标记和扫描GC”(通过向Java命令行添加参数-XX:+UseConcMarkSweepGC;这是最好的,但此GC已从Java 15开始删除),或者通过尝试使用并行GC(通过添加-XX:+UseParallelGC)来运行相同的程序

要解决此问题,请使用上述GC之一,或使用-XX:G1HeapRegionSize参数。将其设置为更大的2次方大小(如2m、4m、16m),以查看是否解决了此问题

有关这方面的更多信息,可以在jxray.com(一个堆转储分析工具)的网站上找到,也可以在一篇关于G1收集器的Oracle文章中找到