直接内存不是JVM规范定义的运行时数据区,而是堆外由操作系统分配的本地内存,通过ByteBuffer.allocateDirect()调用Unsafe.allocateMemory()申请,受-XX:MaxDirectMemorySize限制,超限立即抛OutOfMemoryError: Direct buffer memory。

直接内存(Direct Memory)不是 JVM 规范定义的运行时数据区,而是 JVM 堆外、由操作系统直接分配的一块本地内存。它不归堆管理,也不受 GC 自动回收,但被 JVM 逻辑纳入整体内存管控体系——是 Java 高性能 I/O 的底层支撑,也是线上 OOM 的常见盲区。
它在 JVM 内存结构中“在哪”
JVM 内存区域通常划分为:程序计数器、虚拟机栈、本地方法栈、堆、方法区(元空间),以及直接内存。前五个是《Java 虚拟机规范》明确定义的逻辑区域;而直接内存是 HotSpot 等实现中为支持 NIO 等特性额外使用的堆外空间,不属于任何一项,却与它们协同工作。
它的存在位置是操作系统虚拟地址空间中的一段,和 JVM 进程共存,但独立于 -Xmx 控制的堆。你可以把它看作 JVM “借来用”的一块物理/虚拟内存,JVM 只管登记、限额、触发释放,不管具体搬运。
它怎么分配、谁在用
最常见入口是 ByteBuffer.allocateDirect(size):
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 底层调用 Unsafe.allocateMemory(),走系统 malloc 或 mmap,拿到稳定地址指针
- 同时在堆里创建一个 DirectByteBuffer 对象(约 24 字节),仅保存地址、容量、清理钩子等元信息
- 真正数据存在堆外,GC 不扫描、不移动、不复制——这是零拷贝(如 transferTo)的前提
典型使用场景包括:
- NIO 的 SocketChannel.write() / FileChannel.transferTo()
- Netty 默认的 Unpooled.directBuffer() 或 PooledByteBufAllocator
- 某些数据库驱动(如 pgjdbc)、压缩库(LZ4)、RPC 框架的高性能缓冲区
它怎么被限制和监控
关键参数是 -XX:MaxDirectMemorySize:
- 未设置时,JDK 8+ 默认值 = -Xmx;JDK 7 及以前默认为 0(不限制,风险极高)
- 设为 0 表示禁用检查(慎用,NIO 相关操作会失败)
- 单位支持 k/m/g,例如 -XX:MaxDirectMemorySize=512m
- 每次 allocateDirect 都会同步检查是否超限,超了立刻抛 OutOfMemoryError: Direct buffer memory,不等待 GC
监控不能只靠参数,需实测:
- 启动加 -XX:NativeMemoryTracking=detail,再用
jcmd <pid> VM.native_memory summary查 direct 区用量 - 通过
ManagementFactory.getPlatformMXBean(BufferPoolMXBean.class)获取 direct buffer 统计(含已分配、已使用、总容量) -
jstat -gc <pid>不显示 direct 内存,别被误导
它为什么容易出问题
根本矛盾在于:分配快、释放慢、上限隐晦。
- 分配是即时的系统调用,不触发 GC,也无堆压力反馈
- 释放靠 Cleaner 异步执行 freeMemory(),依赖 GC 发现对象不可达——但 DirectByteBuffer 很小,GC 可能长期不回收它
- 一旦频繁创建又未及时断引用(比如缓存了大量 DirectByteBuffer、或 Netty 未正确 release),就会堆积,直到触达 MaxDirectMemorySize
- 调大 -Xmx 对 Direct OOM 完全无效;堆看着空,进程却因堆外耗尽而崩溃

















