java - 大字节数组使用的堆比预期多

标签 java arrays memory out-of-memory

使用 1 GB Java 堆 ( -Xmx1g ),我将数据存储在许多大型字节数组中。在存储 1 GB 数据之前很长一段时间我收到了 OutOfMemoryError。此时,根据 Runtime rt.maxMemory() - rt.totalMemory() + rt.freeMemory() 计算,还有相当多的空闲堆:

<表类=“s-表”> <标题> 字节数组大小 大约。可存储的数据 大约。显示空闲堆 <正文> 2^18 (262144) 800 MB 270 MB 2^17 (131072) 930 MB 140 MB 2^16 (65536) 997 MB 72 MB 2^15 (32768) 1032 MB 36 MB

为什么大字节数组的堆大小计算关闭,我可以做些什么来修复它吗?


注意:当使用 2^19(或更大)大小的字节数组时,会发生不同的情况:Java byte array of 1 MB or more takes up twice the RAM - 让我们将这个问题集中在 2^18 大小的字节数组上。

在 Windows java -cp .\lib\* -Xmx1g tryit.Main 和 Debian java -cp .:./lib/* -Xmx1g tryit.Main 上使用 64 位服务器 VM AdoptOpenJDK 11.0.11 运行:

package tryit;

public class Main {
    public static void main(String[] args) throws Exception {
        byte[][] array = new byte[1000000][];
        long freeAtStart = free();
        System.out.println("Free at start: " + freeAtStart);
        int chunkSize = 2<<17; // This is 2^18.
        System.out.println("Chunk size   : " + chunkSize);
        for (int n = 0; n < 1000000; n++) {
            if (n % 50 == 0) {
                long currentFree = free();
                System.out.printf("%d: stored %d / allocated %d / free %d\n", n, n * chunkSize, freeAtStart - currentFree, currentFree);
            }
            array[n] = new byte[chunkSize];
        }
    }
    static long free() throws Exception {
        System.gc(); // Called just in case - there should not be anything to garbage collect.
        Thread.sleep(100); // Give GC some time to work
        return Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() + Runtime.getRuntime().freeMemory();
    }
}

最后是四次运行的(缩短的)输出:

2^15:
Free at start: 1068751960 / Chunk size: 32768
31500: stored 1032192000 / allocated 1032933912 / free 35818048

2^16:
Free at start: 1068751960 / Chunk size: 65536
15200: stored 996147200 / allocated 996627400 / free 72124560

2^17:
Free at start: 1068751960 / Chunk size: 131072
7100: stored 930611200 / allocated 930960032 / free 137791928

2^18:
Free at start: 1068751960 / Chunk size: 262144
3050: stored 799539200 / allocated 799823160 / free 268928800

2^19 (humongous objects - allocation size is two times stored size):
Free at start: 1068751960 / Chunk size: 524288
1000: stored 524288000 / allocated 1048811120 / free 19940840

最佳答案

如链接答案 ( Java byte array of 1 MB or more takes up twice the RAM ) 和 G1 garbage collector documentation 中所述G1 垃圾收集器将堆划分为每个 1 MByte(2^20 字节)的区域。对于提供 1024 个区域的 1GB 堆(由于管理开销可能会少一些)。

天真地你会期望 2^20 字节的区域可以容纳 4 个字节数组,每个字节数组为 2^18 字节 - 但不幸的是事实并非如此。字节数组是对象,对象有一个隐藏的对象头(请参阅 https://stackoverflow.com/a/50509263 获取解释)。

所以 byte[262144] 的有效大小不是 262144 字节,而是 262160 字节(取决于 JVM 和最大堆大小,甚至可能更大),这意味着每个区域只能容纳 3 个长度为 262144 的字节数组。

将每个区域 3 个字节数组与 1024 个区域组合起来,对于 1 GB 堆,最多可提供 262144 字节的 3072 个字节数组,这与您的数字非常匹配。


您可以采取什么措施:

  • 使用更大的区域(通过提供 -XX:G1HeapRegionSize=4M ) - 4MB 区域可以容纳长度为 262144 的 15 个字节数组,而 4 个 1MB 区域只能容纳长度为 262144 的 12 个字节数组
  • 使用稍小的字节数组 - 1MB 区域只能容纳长度为 262144 的 3 个字节数组,但可以容纳长度为 262128 的 4 个字节数组

注意:本文使用 2^20 表示二的二十次方,这与 java 表达式 2^20 不同。 ,而是1<<20

关于java - 大字节数组使用的堆比预期多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68331703/

相关文章:

java - 为 keystore.load(InputStream,password) 编写测试用例

linux - 使用父进程和子进程的分治法

python - 如何将 1 大小的 numpy 数组整齐地转换为标量?当输入不是 1 大小的数组时,Numpy "asscalar"会给出错误。

python - 根据对角线值选择子矩阵

c - valgrind 如何计算我的示例内存泄漏?

java - JDBC 库在 Android Studio 中不工作

Java - 如何获取 JTextPane 的默认字体大小

web 文件夹外的 Java Servlet 绝对路径 [ Windows & Linux ]

java - Java 是如何设法在 Applet 模式下使用这么多内存的?

javascript - "JavaScript heap out of memory"传输大文件时