【问题标题】:Why do I get OutOfMemoryError but the heap dump shows a lot of memory as free为什么我得到 OutOfMemoryError 但堆转储显示大量内存是可用的
【发布时间】:2025-12-07 01:10:01
【问题描述】:

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

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

为什么会出现这个OOM,那些int[]数组是怎么回事?

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

【问题讨论】:

    标签: java visualvm eclipse-memory-analyzer


    【解决方案1】:

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

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

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

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

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

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

    更多关于这方面的信息可以在 jxray.com 的网站上找到,一个堆转储分析工具:https://jxray.com/documentation#humongous_objs,以及一篇关于 G1 收集器的 Oracle 文章https://www.oracle.com/technical-resources/articles/java/g1gc.html

    【讨论】:

      最近更新 更多