大字节数组使用比预期更多的堆
Big byte arrays use more heap than expected
使用 1 GB Java 堆 (-Xmx1g
),我将数据存储在许多大字节数组中。在 存储 1 GB 数据之前,我 OutOfMemoryError
相当长一段时间 。那个时候按运行时间rt.maxMemory() - rt.totalMemory() + rt.freeMemory()
:
计算还有相当多的free heap
字节数组大小
大约。可以存储的数据
大约。显示空闲堆
2^18 (262144)
800MB
270MB
2^17 (131072)
930MB
140MB
2^16 (65536)
997MB
72MB
2^15 (32768)
1032MB
36MB
为什么大字节数组的堆大小计算关闭,我可以做些什么来修复它吗?
注意:当使用 2^19(或更大)大小的字节数组时,会发生不同的事情: - 让我们将这个问题集中在 2^18 大小的字节数组上。
运行 使用 64 位服务器 VM AdoptOpenJDK 11.0.11,在 Windows java -cp .\lib\* -Xmx1g tryit.Main
和 Debian java -cp .:./lib/* -Xmx1g tryit.Main
:
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
如链接答案中所述() and the G1 garbage collector documentation G1 垃圾收集器将堆分成每个 1 MByte(2^20 字节)的区域。对于一个 1GB 的堆,它提供 1024 个区域(可能少一点)由于管理开销)。
您可能天真地期望 2^20 字节的区域可以容纳 4 个字节数组,每个数组 2^18 字节 - 但不幸的是,情况并非如此。字节数组是对象,对象有一个隐藏的对象头(解释见)。
所以一个byte[262144]
的有效大小不是262144字节,而是262160字节(根据JVM和最大堆大小可能更大),这意味着每个区域只能容纳长度为 262144 的 3 字节数组。
将每个区域 3 个字节的数组与 1024 个区域相结合,对于 1 GB 的堆,最多可以得到 262144 字节的 3072 字节数组,这与您的数字非常匹配。
你能做些什么:
- 使用更大的区域(通过提供
-XX:G1HeapRegionSize=4M
)- 一个 4MB 的区域可以容纳 15 个长度为 262144 的字节数组,而 4 个 1MB 的区域只能容纳长度为 262144 的 12 个字节的数组
- 使用稍微小一点的字节数组 - 一个 1MB 的区域只能容纳长度为 262144 的 3 个字节数组,但可以容纳长度为 262128 的 4 个字节数组
注意:这个post用2^20表示2的20次方,和java的表达式不一样2^20
,而是 1<<20
使用 1 GB Java 堆 (-Xmx1g
),我将数据存储在许多大字节数组中。在 存储 1 GB 数据之前,我 OutOfMemoryError
相当长一段时间 。那个时候按运行时间rt.maxMemory() - rt.totalMemory() + rt.freeMemory()
:
字节数组大小 | 大约。可以存储的数据 | 大约。显示空闲堆 |
---|---|---|
2^18 (262144) | 800MB | 270MB |
2^17 (131072) | 930MB | 140MB |
2^16 (65536) | 997MB | 72MB |
2^15 (32768) | 1032MB | 36MB |
为什么大字节数组的堆大小计算关闭,我可以做些什么来修复它吗?
注意:当使用 2^19(或更大)大小的字节数组时,会发生不同的事情:
运行 使用 64 位服务器 VM AdoptOpenJDK 11.0.11,在 Windows java -cp .\lib\* -Xmx1g tryit.Main
和 Debian java -cp .:./lib/* -Xmx1g tryit.Main
:
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
如链接答案中所述(
您可能天真地期望 2^20 字节的区域可以容纳 4 个字节数组,每个数组 2^18 字节 - 但不幸的是,情况并非如此。字节数组是对象,对象有一个隐藏的对象头(解释见
所以一个byte[262144]
的有效大小不是262144字节,而是262160字节(根据JVM和最大堆大小可能更大),这意味着每个区域只能容纳长度为 262144 的 3 字节数组。
将每个区域 3 个字节的数组与 1024 个区域相结合,对于 1 GB 的堆,最多可以得到 262144 字节的 3072 字节数组,这与您的数字非常匹配。
你能做些什么:
- 使用更大的区域(通过提供
-XX:G1HeapRegionSize=4M
)- 一个 4MB 的区域可以容纳 15 个长度为 262144 的字节数组,而 4 个 1MB 的区域只能容纳长度为 262144 的 12 个字节的数组 - 使用稍微小一点的字节数组 - 一个 1MB 的区域只能容纳长度为 262144 的 3 个字节数组,但可以容纳长度为 262128 的 4 个字节数组
注意:这个post用2^20表示2的20次方,和java的表达式不一样2^20
,而是 1<<20