java - 如何在 Java 中高效地存储小字节数组?

标签 java arrays memory memory-efficient

字节数组是指长度从 10 到 30 的字节数组。

存储是指将它们存储在RAM中,而不是序列化和持久化到文件系统。

System macOS 10.12.6, Oracle jdk1.8.0_141 64bit, JVM args -Xmx1g

例子: new byte[200 * 1024 * 1024] 的预期行为是 ≈200mb 的堆空间

public static final int TARGET_SIZE = 200 * 1024 * 1024;
public static void main(String[] args) throws InterruptedException {
    byte[] arr = new byte[TARGET_SIZE];
    System.gc();
    System.out.println("Array size: " + arr.length);
    System.out.println("HeapSize: " + Runtime.getRuntime().totalMemory());
    Thread.sleep(60000);
}

jvisualvm total heap usage heap for new byte[200 * 1024 * 1024] jvisualvm memory sample new byte[200 * 1024 * 1024]

但是对于较小的数组,数学并不是那么简单

public static final int TARGET_SIZE = 200 * 1024 * 1024;
public static void main(String[] args) throws InterruptedException {
    final int oneArraySize = 20;
    final int numberOfArrays = TARGET_SIZE / oneArraySize;
    byte[][] arrays = new byte[numberOfArrays][];
    for (int i = 0; i < numberOfArrays; i++) {
        arrays[i] = new byte[oneArraySize];
    }
    System.gc();
    System.out.println("Arrays size: " + arrays.length);
    System.out.println("HeapSize: " + Runtime.getRuntime().totalMemory());
    Thread.sleep(60000);
}

jvisualvm total heap usage heap for 10 * 1024 * 1024 of new byte[20] jvisualvm memory sample for 10 * 1024 * 1024 of new byte[20]

甚至更糟

jvisualvm total heap usage heap for 20 * 1024 * 1024 of new byte[10] jvisualvm memory sample for 20 * 1024 * 1024 of new byte[10]

问题是

这些开销从何而来? 如何有效地存储和使用小字节数组(数据 block )?

更新 1

对于新字节[200*1024*1024][1] 它吃 jvisualvm total heap usage heap for 200 * 1024 * 1024 of new byte[1] jvisualvm memory sample for 200 * 1024 * 1024 of new byte[1]

基本数学表示 new byte[1] 权重 24 个字节。

更新 2

根据What is the memory consumption of an object in Java? Java 中对象的最小大小为 16 字节。从我之前的 "measurements" 24 个字节 -4 个字节用于 int 长度 -1 个实际字节的数据 = 3 个字节的一些 其他垃圾 填充。

最佳答案

好的,所以如果我理解正确(如果不是请询问 - 将尝试回答),这里有几件事。首先是您需要正确的测量工具和JOL是我唯一信任的人。

让我们从简单的开始:

byte[] two = new byte[1];
System.out.println(GraphLayout.parseInstance(one).toFootprint()); 

这将显示 24 字节(12 用于 markclass 字词 - 或对象 header + 4 字节padding),1 byte 为实际值,7 bytes 为 padding(内存为 8 字节对齐)。

考虑到这一点,这应该是一个可预测的输出:

byte[] eight = new byte[8];
System.out.println(GraphLayout.parseInstance(eight).toFootprint()); // 24 bytes

byte[] nine = new byte[9];
System.out.println(GraphLayout.parseInstance(nine).toFootprint()); // 32 bytes

现在让我们转到二维数组:

byte[][] ninenine = new byte[9][9];    
System.out.println(GraphLayout.parseInstance(ninenine).toFootprint()); // 344 bytes

System.out.println(ClassLayout.parseInstance(ninenine).toPrintable());

由于java没有true二维数组;每个嵌套数组本身就是一个具有标题和内容的对象 (byte[])。因此,单个 byte[9] 具有 32 个字节(12 header + 4 填充)和 16字节 用于内容(9 字节 用于 实际 内容 + 7 字节 填充)。

ninenine 对象总共有 56 个字节:16 header + 36 用于保存对九个的引用objects + 4 bytes 用于填充。


在此处查看生成的示例:

byte[][] left = new byte[10000][10];
System.out.println(GraphLayout.parseInstance(left).toFootprint()); // 360016 bytes

byte[][] right = new byte[10][10000];
System.out.println(GraphLayout.parseInstance(right).toFootprint()); // 100216 bytes

增加了 260%;因此,只需更改为其他方式,您就可以节省大量空间。

但更深层次的问题是,Java 中的每个对象都有这些 header ,没有 header 对象还没有。它们可能会出现并被称为 Value Types .可能是在实现的时候——原语数组至少不会有这种开销。

关于java - 如何在 Java 中高效地存储小字节数组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45829943/

相关文章:

java - Java中正则表达式如何表达获取字符串的括号部分

Android - 背景图片占用太多内存

xcode - Swift 字典内存消耗是天文数字

c# - 将二维数组写入文件的最简单方法是什么?

java - 如何在Java中跳过第一个int逐行读取文件到数组?

arrays - 如何高效地检查数组是否包含 brightscript 中的值?

python - 理解python内存分配和释放

java - 在 java 应用程序中保存用户设置的最佳方法是什么?

java - cursor.getString 返回带有有效 uri 的 null

java - 是否可以创建 jmx 子域?