为什么我得到outofmemoryerror,但堆转储显示大量的内存是空闲的

tnkciper  于 2021-06-30  发布在  Java
关注(0)|答案(1)|浏览(423)

我的java程序从一个流中读取数据,并为其中的一部分创建内存缓存。在某个时候,它抛出了一个outofmemoryerror,我让它在那个时候创建了一个堆转储,这样我就可以看到是什么导致了这个问题。但是当我加载堆转储时,我看到大约一半的内存没有使用:我用-xmx8000m启动了vm,当加载到eclipse内存分析器或virtualvm中时,堆转储只显示使用了大约4gb的内存。然而,转储文件本身的文件大小大约为8gb。
同样奇怪的是,这两种工具都将大量int[262136]大小的int数组报告为“未引用对象”,即垃圾。其中大约有4gb—这确实表明它们不是垃圾,而是oom的原因。。顺便说一句,我的代码根本不在这种大小的数组中创建。
我为什么得到这个oom,那些int[]数组是怎么回事?
我运行的是java11jdk,但是java14也出现了同样的问题。

mrzz3bfm

mrzz3bfm1#

这是java的垃圾收集器的问题,很难找到。
这些版本的默认垃圾收集器是g1收集器。此垃圾收集器将可用内存划分为固定大小的内存区域。它们总是2的幂,从1mb开始,取决于-xmx max memory参数。
这些int[262136]数组是gc用来以某种方式将这些区域标记为java对象的技巧。这个int数组正好占用1mb的空间,因此它具有区域的大小。它将它们标记为未引用,因此大多数工具看不到它们,或者将它们标记为垃圾。这是高度误导,因为它似乎是oom问题的原因。
oom的真正原因是缓存代码分配(并释放)被g1垃圾收集器视为“庞大对象”的对象。它在回收或移动这些对象方面存在巨大的问题,这显然会导致内存碎片化——这反过来又会导致oom,即使看起来有足够的内存可用。由于某些原因,gc日志没有给出任何迹象表明这可能是问题8-(。
一个很好的测试,看看这是否是您的问题的原因是运行相同的程序与旧的“标记和扫描gc”(通过添加参数-xx:+useConMarkSweepGC到java命令行;这是最好的方法,但是这个gc已经从java15开始删除了,或者通过尝试使用并行gc(通过添加-xx:+usepallelgc)。
要解决这个问题,可以使用上面的gc之一,或者使用-xx:g1heapregionsize参数。将其设置为更大的2次方大小(如2米、4米、16米)以查看是否解决了问题。
有关这方面的更多信息,请访问jxray.com,这是一个堆转储分析工具:https://jxray.com/documentation#humongous_objs,以及一篇关于https://www.oracle.com/technical-resources/articles/java/g1gc.html.

相关问题