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