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();