Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java byte array of 1 MB or more takes up twice the RAM

Running the code below on Windows 10 / OpenJDK 11.0.4_x64 produces as output used: 197 and expected usage: 200. This means that 200 byte arrays of one million elements take up approx. 200MB RAM. Everything fine.

When I change the byte array allocation in the code from new byte[1000000] to new byte[1048576] (that is, to 1024*1024 elements), it produces as output used: 417 and expected usage: 200. What the heck?

import java.io.IOException;
import java.util.ArrayList;

public class Mem {
    private static Runtime rt = Runtime.getRuntime();
    private static long free() { return rt.maxMemory() - rt.totalMemory() + rt.freeMemory(); }
    public static void main(String[] args) throws InterruptedException, IOException {
        int blocks = 200;
        long initiallyFree = free();
        System.out.println("initially free: " + initiallyFree / 1000000);
        ArrayList<byte[]> data = new ArrayList<>();
        for (int n = 0; n < blocks; n++) { data.add(new byte[1000000]); }
        System.gc();
        Thread.sleep(2000);
        long remainingFree = free();
        System.out.println("remaining free: " + remainingFree / 1000000);
        System.out.println("used: " + (initiallyFree - remainingFree) / 1000000);
        System.out.println("expected usage: " + blocks);
        System.in.read();
    }
}

Looking a bit deeper with visualvm, I see in the first case everything as expected:

byte arrays take up 200mb

In the second case, in addition to the byte arrays, I see the same number of int arrays taking up the same amount of RAM as the byte arrays:

int arrays take up additional 200mb

These int arrays, by the way, do not show that they are referenced, but I can't garbage collect them... (The byte arrays show just fine where they are referenced.)

Any ideas what is happening here?

like image 765
Georg Avatar asked Oct 22 '19 14:10

Georg


People also ask

How much memory does a byte take Java?

Yes, a byte variable in Java is in fact 4 bytes in memory.

What happens if you exceed the size of an array in Java?

If you try to access the array position (index) greater than its size, the program gets compiled successfully but, at the time of execution it generates an ArrayIndexOutOfBoundsException exception.

How much can a byte array hold?

MAX_VALUE or about 2 billion regardless of the type of the array.

What is the maximum size of byte array in Java?

Max Size It generally depends on the JVM that we're using and the platform. Since the index of the array is int, the approximate index value can be 2^31 – 1. Based on this approximation, we can say that the array can theoretically hold 2,147,483,647 elements.


1 Answers

What this describes is the out-of-the-box behaviour of the G1 garbage collector which commonly defaults to 1MB "regions" and became a JVM default in Java 9. Running with other GCs enabled gives varying numbers.

any object that is more than half a region size is considered "humongous"... For objects that are just slightly larger than a multiple of the heap region size, this unused space can cause the heap to become fragmented.

I ran java -Xmx300M -XX:+PrintGCDetails and it shows heap is exhausted by humongous regions:

[0.202s][info   ][gc,heap        ] GC(51) Old regions: 1->1
[0.202s][info   ][gc,heap        ] GC(51) Archive regions: 2->2
[0.202s][info   ][gc,heap        ] GC(51) Humongous regions: 296->296
[0.202s][info   ][gc             ] GC(51) Pause Full (G1 Humongous Allocation) 297M->297M(300M) 1.935ms
[0.202s][info   ][gc,cpu         ] GC(51) User=0.01s Sys=0.00s Real=0.00s
...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

We want our 1MiB byte[] to be "less than half the G1 region size" so adding -XX:G1HeapRegionSize=4M gives a functional application:

[0.161s][info   ][gc,heap        ] GC(19) Humongous regions: 0->0
[0.161s][info   ][gc,metaspace   ] GC(19) Metaspace: 320K->320K(1056768K)
[0.161s][info   ][gc             ] GC(19) Pause Full (System.gc()) 274M->204M(300M) 9.702ms
remaining free: 100
used: 209
expected usage: 200

In depth overview of G1: https://www.oracle.com/technical-resources/articles/java/g1gc.html

Crushing detail of G1: https://docs.oracle.com/en/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552

like image 108
drekbour Avatar answered Oct 04 '22 07:10

drekbour