`ByteBuffer.allocateDirect`和Xmx

luk*_*keg 4 java memory-management

根据各种 来源(虽然在JavaDoc中没有特别提到),ByteBuffer.allocateDirect从主JVM堆中分配内存.我可以确认使用Java Mission Control,看到调用的程序ByteBuffer n = ByteBuffer.allocateDirect(Integer.MAX_VALUE)没有使用大量的Java Heap内存:

在此输入图像描述

但是,当限制JVM堆内存时,此堆外内存分配将停止工作.例如,当我使用-Xmx1g选项运行JVM时,allocateDirect调用会导致以下异常:Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory.我不完全理解这个JVM选项如何与堆外直接内存分配相关,因为 - 根据文档 - 该-Xmx选项设置Java堆空间大小.如果我使用getUnsafe().allocateMemory(Integer.MAX_VALUE);内存分配内存成功分配.我的JVM如下:

java版"10"2018-03-20 Java(TM)SE运行时环境18.3(版本10 + 46)Java HotSpot(TM)64位服务器VM 18.3(版本10 + 46,混合模式)

就是这种行为之间的XmxByteBuffer.allocateDirect预期?

编辑:JDK 1.7中似乎存在(不可重现的)错误,其行为与上述相同.这是一个错误吗?

Jac*_* G. 11

我不得不寻找寻找原因的寻宝者,但是你走了!

首先,我看了一下ByteBuffer#allocateDirect,发现了以下内容:

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}
Run Code Online (Sandbox Code Playgroud)

然后我导航到构造函数DirectByteBuffer并找到以下方法调用:

Bits.reserveMemory(size, cap);
Run Code Online (Sandbox Code Playgroud)

看看这种方法,我们看到:

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");
Run Code Online (Sandbox Code Playgroud)

这似乎是你收到此错误的地方,但现在我们需要弄清楚它为什么会引起.为此,我查看了调用tryReserveMemory并找到了以下内容:

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;
}
Run Code Online (Sandbox Code Playgroud)

我对这个maxMemory领域很好奇,并且看着它被宣布的地方:

private static volatile long maxMemory = VM.maxDirectMemory();
Run Code Online (Sandbox Code Playgroud)

现在我不得不看看maxDirectMemory内部VM.java:

public static long maxDirectMemory() {
    return directMemory;
}
Run Code Online (Sandbox Code Playgroud)

最后,让我们来看看声明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;
Run Code Online (Sandbox Code Playgroud)

嘿,看那个!如果不使用手动指定"-XX:MaxDirectMemorySize=<size>",则默认为Runtime.getRuntime().maxMemory(),即您设置的堆大小.

看起来-Xmx1G小于Integer.MAX_VALUE字节,调用tryReserveMemory永远不会返回true,这导致sleeps >= MAX_SLEEPS,打破while循环,抛出你的OutOfMemoryError.

如果我们看一下Runtime.getRuntime().maxMemory(),那么如果你没有指定最大堆大小,我们就会明白为什么它可以工作:

/**
 * 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();
Run Code Online (Sandbox Code Playgroud)