direct memory是jvm堆外由os直接分配的内存,通过bytebuffer.allocatedirect()申请、cleaner异步释放;相比堆缓冲区可减少i/o拷贝次数,提升吞吐与延迟,但分配成本高、需显式配置-xx:maxdirectmemorysize防oom。

Direct Memory 就是 JVM 堆外、由操作系统直接分配的内存,Java 代码不能直接 malloc/free,而是靠 ByteBuffer.allocateDirect() 申请,靠 Cleaner 虚引用异步释放——它不是“新内存”,而是绕过堆的一条高速通道。
为什么用 ByteBuffer.allocateDirect() 而不是普通 ByteBuffer.allocate()
核心区别在 I/O 路径:普通堆缓冲区(HeapByteBuffer)做网络或文件读写时,数据要从 Java 堆 → JVM 本地内存 → 操作系统内核(两次拷贝);而 allocateDirect() 分配的 DirectByteBuffer,其底层内存地址可被 OS 直接访问,省掉中间一次拷贝,尤其在网络高吞吐(如 Netty)、大文件顺序读写场景下,延迟更低、吞吐更高。
- 典型现象:用堆缓冲跑千兆网卡压测,CPU 花在 copy 阶段明显偏高;换 direct 后 sys CPU 下降 20%~40%
- 注意:
DirectByteBuffer对象本身仍在堆里(只占几十字节),真正的大块内存不在堆中,所以 GC 日志里看不到它,但OutOfMemoryError: Direct buffer memory会照常抛 - 分配成本高:每次调用
allocateDirect()都触发一次Unsafe.allocateMemory()系统调用,比堆分配慢一个数量级,别在循环里反复 new
-XX:MaxDirectMemorySize 不设等于自找麻烦
这个参数不显式设置时,默认值 = -Xmx(最大堆大小),但物理内存 ≠ 堆内存 —— 很多服务堆只设 4G,却跑着 10G 的 direct buffer,结果 OutOfMemoryError: Direct buffer memory 突然爆发,而堆内存监控还绿油油的。
- 必须按实际 workload 预估:比如 Netty 默认每连接 16KB direct buffer,10 万连接就是 1.6GB,再加预留,建议设为
-XX:MaxDirectMemorySize=2g - Linux 下可通过
cat /proc/<pid>/status | grep -i "nonheap"</pid>或 JMX 的java.nio.BufferPool.direct查实时用量 - 如果用到了
Unsafe手动分配(如某些序列化库),这部分不受MaxDirectMemorySize约束,得单独盯
释放不靠你“记得”,但靠你不乱搞
DirectByteBuffer 的回收走的是 Cleaner 机制:对象被 GC 后,虚引用入队,后台 ReferenceHandler 线程调 clean() → Unsafe.freeMemory()。这机制可靠,但有两个致命干扰点:
立即学习“Java免费学习笔记(深入)”;
- 强引用泄漏:比如把
DirectByteBuffer放进静态ConcurrentHashMap又忘了 remove,GC 不掉它,Cleaner 永远不触发 → 内存缓慢泄漏 - 显式调用
((DirectBuffer) buf).cleaner().clean()是危险操作:一旦 buf 后续又被使用,就变成 use-after-free,可能 crash 或静默数据错乱(JDK 9+ 已对多次 clean 加保护,但旧版本仍存在风险) - 更稳妥的做法:用 try-with-resources 包裹(JDK 13+
Cleanable接口支持),或确保 buffer 生命周期受控(如 Netty 的PooledByteBufAllocator)
最常被忽略的点:直接内存泄漏不会出现在堆 dump 里,jstat -gc 也看不出异常,得靠 jcmd <pid> VM.native_memory summary</pid> 或 Native Memory Tracking(NMT)开启后查 internal 和 direct 区域增长趋势——没开 NMT,问题来了只能重启看日志猜。








