5

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?

lukeg
  • 4,189
  • 3
  • 19
  • 40

2 Answers2

12

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();
Jacob G.
  • 28,856
  • 5
  • 62
  • 116
-1

Well, this is not a bug, let me tell you why the allocateDirect causes OOM. In JVM, you already know -Xmx option that sets the Max JVM Heap Size. But you have to know that JVM Memory(heap) is also a part of your PC memory. In simple terms, this means Your PC Memory = JVM + Direct Memory. So when you set JVM option to '-Xmx1g' and get OOM exception, check be sure you have enough memory(the rest of your PC memory) to run allocateDirect.

Hope it will help you!

Kevinsss
  • 754
  • 7
  • 7