直接内存是JVM外由操作系统管理的本地内存,不属JVM运行时数据区,不走GC流程;通过ByteBuffer.allocateDirect()分配,由Cleaner异步回收,需用-XX:MaxDirectMemorySize限制并配合NMT监控。

直接内存不属于 JVM 规范定义的运行时数据区,它不归堆、栈、方法区等任何 Java 内存区域管辖,而是 JVM 之外的本地内存(Native Heap),由操作系统直接管理。它的分配与回收不走 GC 流程,也不受 -Xmx 等堆参数控制。
直接内存的分配方式
Java 中主要通过 java.nio.ByteBuffer.allocateDirect() 触发分配。底层调用的是操作系统的 malloc 或 DirectByteBuffer 构造时的 Unsafe.allocateMemory()。JVM 会记录这部分内存占用(可通过 -XX:MaxDirectMemorySize 限制,默认等于 -Xmx),但实际内存申请发生在本地堆。
- 每次 allocateDirect() 都会向 OS 申请一块连续的本地内存,并创建一个 DirectByteBuffer 对象作为“句柄”;
- 该 ByteBuffer 的 cleaner(基于 Cleaner 类)会被注册到一个内部的清理队列,用于后续异步回收;
- 分配失败时抛出 OutOfMemoryError: Direct buffer memory,而非堆溢出错误。
直接内存的回收机制
直接内存不会被垃圾收集器自动回收,它的释放依赖于 DirectByteBuffer 对象的可达性状态 + Cleaner 的后台清理线程。当 DirectByteBuffer 对象被 GC 判定为不可达后,其关联的 Cleaner 会被加入待执行队列,由 Reference Handler 线程 调用 clean() 方法,最终执行 Unsafe.freeMemory() 归还本地内存。
- 这个过程是异步的,存在延迟:对象已不可达,但本地内存可能数秒甚至更久才真正释放;
- 若大量 DirectByteBuffer 频繁创建又未及时被 GC(比如长期持有引用、缓存未清理),极易引发本地内存耗尽;
- 无法通过 System.gc() 强制触发清理,因为 Cleaner 不属于可显式干预的引用类型。
常见问题与规避建议
生产环境中直接内存泄漏比堆泄漏更隐蔽,排查需结合 NMT(Native Memory Tracking)和 jcmd 工具。
立即学习“Java免费学习笔记(深入)”;
- 启用 NMT:启动时加 -XX:NativeMemoryTracking=detail,再用 jcmd
VM.native_memory summary 查看 direct memory 分配总量; - 避免手动管理:不要在 finally 块中反复调用 freeMemory(),Cleaner 已内置保障;
- 限制上限:务必设置 -XX:MaxDirectMemorySize,防止无节制增长拖垮整个进程或系统;
- 注意 Netty 等框架:它们大量使用池化 DirectByteBuffer,需关注其 Recycler 和 ResourceLeakDetector 配置。
直接内存不是“省堆”的捷径,而是有明确适用场景(如零拷贝 IO)的特殊资源,用得对才能提效,用错了反而成为稳定性隐患。


















