最近在重新阅读一遍《深入理解 Java 虚拟机》,因为现在一直用着Java 8环境,就用Java 8跑一下书上的例子。不过,书上是基于Java 6/7,经过Java 8的改进,很多例子都没法测试,主要是方法区/永久区的例子,在Java 8中是元数据区。不过本文是模拟直接内存OOM的例子。
书上的例子调用unsafe.allocateMemory()方法分配内存,但是没有触发OOM,程序正常运行。
/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
* @author zzm
*/
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
在StackOverflow上找到别人的提问:Why XX:MaxDirectMemorySize can’t limit Unsafe.allocateMemory?,遇到了和我一样的问题。下面说一下具体的原因:
Unsafe.allocateMemory()是系统调用的os::malloc一个包装,并没有关心VM要求的内存限制,所以也就绕过了MaxDirectMemorySize的限制。
而ByteBuffer.allocateDirect()在会在调用这个方法之前,调用Bits.reserveMemory(),这个方法将检查进程的内存占用情况并抛出异常。
修改后的代码如下:
/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
* @author zzm
*/
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
List<ByteBuffer> list = new ArrayList<>();
while (true) {
// unsafe.allocateMemory(_1MB);
list.add(ByteBuffer.allocateDirect(_1MB));
}
}
}
输出:
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:694)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at com.argsno.understaingjvm.ch2.DirectMemoryOOM.main(DirectMemoryOOM.java:24)