ByteBuffer.allocateDirect与-Xmx的关联异常是否为预期行为?
咱们来一步步拆解这个问题背后的原因:
1. Direct ByteBuffer与Unsafe.allocateMemory的内存管理差异
ByteBuffer.allocateDirect:它分配的堆外内存是受JVM管控的,JVM会维护一个直接内存的上限(由-XX:MaxDirectMemorySize参数控制)。默认情况下,这个值和-Xmx设置的堆内存大小一致(比如你设了-Xmx1g,默认直接内存上限也是1g左右)。
另外,虽然内存是在堆外,但JVM会在堆中创建一个很小的DirectByteBuffer对象作为引用,这就是你用Java Mission Control看不到大量堆内存占用的原因——真正的内存不在堆里。Unsafe.allocateMemory:这个方法直接绕过了JVM的内存管理机制,调用底层操作系统的malloc函数分配内存。它不受JVM的MaxDirectMemorySize限制,只受系统可用的物理内存/虚拟内存总量约束。但代价是JVM不会自动回收这部分内存,必须手动调用Unsafe.freeMemory释放,否则会造成内存泄漏。
2. 为什么会触发OutOfMemoryError?
当你设置-Xmx1g时,默认的直接内存上限也被限制在1g左右,而Integer.MAX_VALUE对应的是约2GB的内存(2147483647字节),明显超过了直接内存的上限,所以allocateDirect会抛出java.lang.OutOfMemoryError: Direct buffer memory异常,这完全符合JVM的设计预期。
而Unsafe.allocateMemory因为不受JVM直接内存限制,只要你的系统有足够的虚拟内存(比如64位系统通常有很大的虚拟内存空间),就能成功分配这2GB内存。
3. 关于JDK 1.7的不可复现BUG
早期JDK 1.7确实存在过直接内存管理相关的问题,比如某些场景下MaxDirectMemorySize的限制没有生效,但这些问题在后续的JDK版本(包括Java 10)中已经被修复,现在的行为是符合规范的,不属于BUG。
内容的提问来源于stack exchange,提问作者lukeg

