Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`ByteBuffer.allocateDirect` and Xmx

According to various sources (though not specifically mentioned in JavaDoc), ByteBuffer.allocateDirect allocates the memory off the main JVM heap. I can confirm that using Java Mission Control, seeing that the program that calls ByteBuffer n = ByteBuffer.allocateDirect(Integer.MAX_VALUE) does not use much of Java Heap memory:

enter image description here

However, this off-heap memory allocation stops working when one limits JVM heap memory. For example, when I run the JVM with -Xmx1g option, the allocateDirect call causes the following exception: Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory. I do not fully understand how can this JVM option pertain to off-heap direct memory allocation, as - according to the documentation - the -Xmx option sets the Java heap space size. If I allocate the memory using getUnsafe().allocateMemory(Integer.MAX_VALUE); the memory is allocated successfully. My JVM is as follows:

java version "10" 2018-03-20 Java(TM) SE Runtime Environment 18.3 (build 10+46) Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode)

Is this kind of behaviour between Xmx and ByteBuffer.allocateDirect expected?

EDIT: There seemed to be a (non-reproducible) bug in JDK 1.7 with the same behaviour as described above. So is this a bug?

like image 778
lukeg Avatar asked Dec 24 '22 07:12

lukeg


1 Answers

I had to go on a scavenger hunt to find the reason, but here you go!

First, I looked at ByteBuffer#allocateDirect and found the following:

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

I then navigated to the constructor of DirectByteBuffer and found the following method call:

Bits.reserveMemory(size, cap);

Looking in this method, we see:

while (true) {
    if (tryReserveMemory(size, cap)) {
        return;
    }

    if (sleeps >= MAX_SLEEPS) {
        break;
    }

    try {
        if (!jlra.waitForReferenceProcessing()) {
            Thread.sleep(sleepTime);
            sleepTime <<= 1;
            sleeps++;
        }
    } catch (InterruptedException e) {
        interrupted = true;
    }
}

// no luck
throw new OutOfMemoryError("Direct buffer memory");

This seems to be where you received this error, but now we need to figure out why it's caused. For that, I looked into the call to tryReserveMemory and found the following:

private static boolean tryReserveMemory(long size, int cap) {
    long totalCap;

    while (cap <= maxMemory - (totalCap = totalCapacity.get())) {
        if (totalCapacity.compareAndSet(totalCap, totalCap + cap)) {
            reservedMemory.addAndGet(size);
            count.incrementAndGet();
            return true;
        }
    }

    return false;
}

I was curious about the maxMemory field, and looked to where it was declared:

private static volatile long maxMemory = VM.maxDirectMemory();

Now I had to look at the maxDirectMemory within VM.java:

public static long maxDirectMemory() {
    return directMemory;
}

Finally, let's look at the declaration of directMemory:

// A user-settable upper limit on the maximum amount of allocatable direct
// buffer memory.  This value may be changed during VM initialization if
// "java" is launched with "-XX:MaxDirectMemorySize=<size>".
//
// The initial value of this field is arbitrary; during JRE initialization
// it will be reset to the value specified on the command line, if any,
// otherwise to Runtime.getRuntime().maxMemory().
//
private static long directMemory = 64 * 1024 * 1024;

Hey, look at that! If you don't manually specify this using "-XX:MaxDirectMemorySize=<size>", then it defaults to Runtime.getRuntime().maxMemory(), which is the heap size that you set.

Seeing as -Xmx1G is smaller than Integer.MAX_VALUE bytes, the call to tryReserveMemory will never return true, which results in sleeps >= MAX_SLEEPS, breaking out of the while-loop, throwing your OutOfMemoryError.

If we look at Runtime.getRuntime().maxMemory(), then we see why it works if you don't specify the max heap size:

/**
 * Returns the maximum amount of memory that the Java virtual machine
 * will attempt to use.  If there is no inherent limit then the value
 * {@link java.lang.Long#MAX_VALUE} will be returned.
 *
 * @return  the maximum amount of memory that the virtual machine will
 *          attempt to use, measured in bytes
 * @since 1.4
 */
public native long maxMemory();
like image 98
Jacob G. Avatar answered Jan 09 '23 04:01

Jacob G.