【问题标题】:Memory usage of byte array in Java [duplicate]Java中字节数组的内存使用情况[重复]
【发布时间】:2023-03-11 19:17:02
【问题描述】:

对于启发式预计算表,我需要一个包含 1504935936 个条目的字节数组。这应该需要大约 1.5 GB 的内存。

public class Main{
    public static void main(String[] args){
        byte[] arr = new byte[1504935936];
    }
}

如果我为程序提供 2 GB 的 RAM,为什么会出现“OutOfMemoryError: Java heap space”-异常

java -Xmx2048M Main

java -Xmx2153M Main

它有效。为什么需要这么多内存?

【问题讨论】:

  • 要分配一个对象,堆中必须有那么多可用的连续空间。如果堆是 2G,那么几乎不可能有 1.5G 的连续空间。 (此外,各种平台对单个对象的最大大小有其他限制,具体取决于它们的地址转换硬件的工作方式。)
  • @vanza - 几乎所有东西都有一个例外。但是任何允许不连续分配数组的 JVM 都可能是为微型系统设计的。允许这样做会对性能产生重大(负面)影响。
  • 只是出于好奇,您在做什么需要特定数量的条目?

标签: java arrays memory ram


【解决方案1】:

可能是因为 Java 堆正被您程序中的其他数据使用和碎片化。

该字节数组需要在 Java 堆空间中分配为一个连续的 1.5 GB 内存块。 (这不是 Java 语言规范所要求的,但 AFAIK 是所有当前 JVM 实现实际工作的方式。)您的一些堆空间正在被消耗,并且 - 可能更重要的是 - 被之前程序中发生的其他内存分配碎片化分配那个大字节数组。 java -Xmx2153M Main 可能是您必须使整个堆有多大才能在您进行分配时留下连续的 1.5 GB 空间。

如果您将这个大数组分成 100 个大小为 1/100 的较小数组,它可能适合较小的堆,因为它对堆碎片不那么敏感。

【讨论】:

【解决方案2】:

这里的其他帖子有一些很好的信息,但他们错过了一个关键点:

获取一个好的内存分析器(最好是带有可视显示的)并将其附加到您的 jvm。您将看到现代 jvm 没有一个大堆空间,而是有多个池(也称为世代)。通常,“老一代”是最大的,但您也会有一些其他的。 加在一起,所有这些池的总和应该大约是您为 jvm 允许的堆空间。

因此,您的“-Xmx2048M”设置不会导致具有单个池的堆,它可以支持 1.5GB 数组(正如其他人所指出的,您需要一个连续的内存块用于数组,即完全包含在单个池/代中的一块内存)。

【讨论】:

    【解决方案3】:

    如果进程作为 32 位进程执行,大多数操作系统只为进程保留大约 2GB 的地址空间,另外 2GB 的地址空间被映射用于内核内容(这样当您的进程调用内核内容时,您不必执行尽可能多的上下文切换)。

    即使您的机器有 8 GB 内存,或 2 GB 内存和 2 GB 交换空间,每个 32 位进程也只能分配和寻址 2 GB,除非您使用 PAE 或类似方法。

    这会导致一些问题。一,您可能没有足够的原始地址空间来存储所有分配的总大小。第二,你可能没有一个连续的内存块是你需要的数组大小 - Java 和其他几个 VM 环境使用单独的堆来存储不同类型的内存,例如,与 gen 0 分开的大型对象堆,或者第 1 代等对象。每个分区都会导致更小的连续区域。

    在 64 位进程中,地址空间限制几乎没有了,但是,您可能仍然没有足够的连续、可提交、java 允许的内存来满足请求。如果您将 Java 设置为只允许总共 2GB 内存,您可能仍然无法找到足够的连续内存来满足请求。

    请记住,该进程确实需要相当大的内存块来存储您的程序的代码页,并且需要用于 java 运行时的内存。仅此一项就可能有几百兆内存,具体取决于程序其余部分的需求。

    在分配一个 1 元素字节数组时执行您的简单程序,并使用 SysInternal 的 VMMap 检查内存以了解您的内存开销来自何处(不包括您的大分配),这可能是有指导意义的。

    然后试一试你的大额分配,看看你会得到什么。

    【讨论】:

    • 根据发帖者的描述,这个 2GB 的限制可能不相关:他们说它适用于 java -Xmx2153M Main,因此 JVM 进程能够从系统分配那么多连续的内存。跨度>
    • @AndrewJanke - 如果没有足够的连续内存来分配 1.5 GB 数组,则 GB 限制是相关的,无论导致 GB 限制的原因是什么。其中一个原因是 32 位地址空间;另一个这样的原因是手动 Java 内存限制选项。我提供了一个解决中间和根本原因的回应。
    【解决方案4】:

    jmapjhat 是发现谁在使用哪些内存部分的好命令。我建议从堆转储开始并查看这些。在 Java 中,只有部分可用内存分配给堆。还需要运行 VM 的内存和堆栈空间。堆也被分成几部分。 OutOfMemoryException 在一部分填满(终身代)时给出。堆分析器工具将帮助您确定到底发生了什么。

    为了更快,您还可以在分配数组之前尝试检查这些值:

    Runtime.getRuntime().totalMemory();
    Runtime.getRuntime().freeMemory();
    

    以下是一些更有用的链接,可获取有关内存使用的更多信息:

    【讨论】:

      【解决方案5】:

      JVM 内存空间分为几个区域。

      使用选项-Xmx 设置Java 堆的大小,HotSpot 的堆由四个空间构成,Eden、Survivor 1 和2 以及tenured。

      要记住的是,第一棵树指的是 young 空间,其余的称为 old

      default young space 消耗 -Xmx 值的 1/3。

      然后意味着当您声明 -Xmx 2g 时。那个年轻的空间将消耗超过 600mb。

      有了这么大的数据,您可以考虑使用Direct ByteBuffer,Peter 描述了here

      IntBuffer arr = ByteBuffer.allocateDirect(size)
                                  .order(ByteOrder.nativeOrder()).asIntBuffer(); 
       arr.put(n, 1);// arr[n] = 1
       arr.get(n);   // arr[n]
      

      Java - Heap vs Direct memory access


      要诊断 Oracle VM 上 HotSpot 上的应用程序如何使用 Java 堆,您可以找到与 SDK 一起提供的名为 jstat 的工具。这个工具可以让你快速反馈你的应用程序发生了什么。

      在您的情况下,您最感兴趣的选项是gccapacity,它提供有关内存池生成和空间容量的数据和gcutil 以及垃圾收集统计摘要 .

      感谢 gccapacity,您将了解以下内容的最大容量(以 KB 为单位):

      • NGCMX - 新一代(伊甸园)
      • S0CMX - 幸存者空间 0
      • S1CMX - 幸存者空间 0
      • OGCMX - 最大老年代

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-12-24
        • 1970-01-01
        • 1970-01-01
        • 2020-12-19
        • 2016-01-20
        • 1970-01-01
        • 2013-08-13
        相关资源
        最近更新 更多