为什么XX:MaxDirectMemorySize不能限制Unsafe.allocateMemory?

12

以下代码将分配大量的直接内存,但不会导致java.lang.OutOfMemoryError: Direct buffer memory错误:

//JVM args: -Xms10m -Xmx10m -XX:MaxDirectMemorySize=10m
    public class DirectMemoryOOM {
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            Field f = Unsafe.class.getDeclaredFields()[0];
            f.setAccessible(true);
            Unsafe us = (Unsafe) f.get(null);
            long size = 1024 * 1024 * 1024;
            while (true) {
                long p = us.allocateMemory(size);
                for (int i = 0; i < size; i++) {
                    us.putByte(p + i, Byte.MAX_VALUE);
                }
            }
        }
    }

但是接下来的代码会出现java.lang.OutOfMemoryError: Direct buffer memory错误。 我已经看到了来自Java unsafe memory allocation limit的答案,但是ByteBuffer.allocateDirect是使用Unsafe.allocateMemory()实现的。

//JVM args: -Xms10m -Xmx10m -XX:MaxDirectMemorySize=10m
public class DirectMemoryOOM {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        int size = 1024 * 1024;
        System.out.println(sun.misc.VM.maxDirectMemory());
        while (true) {
            ByteBuffer.allocateDirect(size);
        }
    }
}

为什么极限失败会发生在第一个?

2个回答

12

正如原回答所说:Unsafe.allocateMemory() 是对 os::malloc 的封装,它不关心虚拟机强制实施的任何内存限制。

ByteBuffer.allocateDirect() 会调用此方法,但在此之前,它将调用 Bits.reserveMemory()(在我使用的 Java 7 版本中是 DirectByteBuffer.java:123),该方法检查进程的内存使用情况并抛出您提到的异常。


谢谢你的回答! - Qy Zuo

3
错误来自于调用 allocateDirect 时,在调用 unsafe.allocateMemory(size) 之前,调用了 Bits.reserveMemory 方法。 在调用 reserveMemory 方法时会进行如下验证:
synchronized (Bits.class) {
    if (totalCapacity + cap > maxMemory)
        throw new OutOfMemoryError("Direct buffer memory");
    reservedMemory += size;
    totalCapacity += cap;
    count++;
}

如果所需分配的内存高于从maxMemory检索到的内存,则会引发错误。

maxMemory = VM.maxDirectMemory();

直接调用allocateMemory方法会执行本地方法,并且不会验证最大容量(这就解释了为什么您在第一个片段中没有收到错误),这是--XX:MaxDirectMemorySize的主要目标,正如reserveMemory中的评论所解释的那样。

// -XX:MaxDirectMemorySize limits the total capacity rather than the
// actual memory usage, which will differ when buffers are page
// aligned.
if (cap <= maxMemory - totalCapacity) {
     reservedMemory += size;
     totalCapacity += cap;
     count++;
     return;
}

值得一提的是,你的第一个片段实现并不是一个好的实践。在 Bits.java 中的注释指出,无论何时直接内存被分配,都应该调用 reserveMemory:
// These methods should be called whenever direct memory is allocated or
// freed.  They allow the user to control the amount of direct memory
// which a process may access.  All sizes are specified in bytes.
static void reserveMemory(long size, int cap) {

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