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:

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?
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With