`ByteBuffer.allocateDirect` and Xmx

6
根据各种 来源(虽然JavaDoc中没有特别提到),ByteBuffer.allocateDirect 分配的内存不在主JVM堆上。我可以通过使用Java Mission Control来确认,调用 ByteBuffer n = ByteBuffer.allocateDirect(Integer.MAX_VALUE) 的程序并不使用太多Java堆内存:

enter image description here

然而,当限制JVM堆内存时,这种离堆内存分配就会停止工作。例如,当我使用-Xmx1g选项运行JVM时,allocateDirect调用会导致以下异常:Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory。我不完全理解这个JVM选项如何涉及到离堆直接内存分配,因为根据文档-Xmx选项设置Java堆空间大小。如果我使用getUnsafe().allocateMemory(Integer.MAX_VALUE);来分配内存,则可以成功分配内存。我的JVM版本如下:

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)

这种XmxByteBuffer.allocateDirect之间的行为是否符合预期?

编辑:似乎在JDK 1.7中存在一个(无法再现的)bug,其行为与上述描述相同。那么这是一个错误吗?

2个回答

13

我不得不进行一次寻宝游戏来找到原因,但是现在已经找到了!

首先,我查看了ByteBuffer#allocateDirect并找到了以下内容:

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

我随后导航到DirectByteBuffer的构造函数并发现以下方法调用:

Bits.reserveMemory(size, cap);

通过这种方式观察,我们可以看到:

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

看起来这就是您收到此错误的地方,但现在我们需要弄清楚它的原因。为此,我查看了对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;
}

我对maxMemory字段很好奇,然后查看了声明它的位置:

private static volatile long maxMemory = VM.maxDirectMemory();

现在我需要查看 VM.java 中的 maxDirectMemory:

public static long maxDirectMemory() {
    return directMemory;
}

最后,让我们看一下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;

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

既然-Xmx1GInteger.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();

1
这太棒了,谢谢。 - Himanshu Ahire

-1

嗯,这不是一个 bug,让我告诉你为什么 allocateDirect 会导致 OOM

在 JVM 中,您已经知道 -Xmx 选项设置了最大 JVM 堆大小。但是您必须知道 JVM 内存(堆)也是您的 PC 内存的一部分。

简单来说,这意味着 您的 PC 内存 = JVM + 直接内存

因此,当您将 JVM 选项设置为 '-Xmx1g' 并且出现 OOM 异常时,请确保您有足够的内存(PC 内存的剩余部分)来运行 allocateDirect

希望这能帮到您!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接