直接内存是jvm堆外由操作系统分配的内存,通过bytebuffer.allocatedirect()调用native方法(如malloc/mmap)分配,不受-xmx限制但受-xx:maxdirectmemorysize管控,无gc自动回收,依赖cleaner异步释放,频繁分配未释放或cleaner延迟会触发outofmemoryerror: direct buffer memory。

直接内存不是 JVM 堆的一部分,它由操作系统直接分配,常用于 NIO 的高性能 I/O 场景。分配不当或未及时释放,容易触发 java.lang.OutOfMemoryError: Direct buffer memory。关键在于理解它的独立性、不受 GC 管理、以及与系统内存的强绑定关系。
Direct Memory 是什么?怎么分配出来的?
直接内存是堆外内存,通过 ByteBuffer.allocateDirect() 分配,底层调用的是操作系统的 native 内存(如 Linux 的 mmap 或 malloc)。它不经过 JVM 堆,因此:
- 不受
-Xmx/-Xms控制,但受-XX:MaxDirectMemorySize限制(默认等于堆最大值) - 分配成本高(涉及系统调用),但读写性能好(避免 Java 堆与内核缓冲区之间的数据拷贝)
- 没有 GC 自动回收机制,依赖
Cleaner(JDK 9+)或finalize(旧版本)异步清理,延迟不可控
为什么会出现 Direct Memory OOM?
溢出本质是操作系统无法再分配足够连续的 native 内存,常见原因有:
- 频繁调用
allocateDirect()且未显式清理(比如 ByteBuffer 没被 GC 回收,或 Cleaner 执行滞后) - 设置了过小的
-XX:MaxDirectMemorySize(例如设为 64m,但业务单次需 100m) - 大量 DirectByteBuffer 对象长期被引用(如缓存到静态集合、线程局部变量未清理)
- 应用运行时间长 + 高并发 NIO 操作(如 Netty 服务端未合理复用 Buffer)
如何排查 Direct Memory 泄漏或溢出?
不能靠常规堆 dump 工具(如 MAT)直接看到 Direct Memory 占用,需结合多维度指标:
- 查看 JVM 启动参数是否含
-XX:MaxDirectMemorySize,确认配置是否合理 - 用
jstat -gc <pid></pid>观察CCST(压缩类空间)、YGC/FGC频率,间接判断是否因 DirectBuffer 清理慢拖累 GC - 用
jcmd <pid> VM.native_memory summary scale=MB</pid>查看 native 内存总用量及 “direct” 子项(需开启-XX:NativeMemoryTracking=summary启动) - 用
jmap -histo:live <pid></pid>统计java.nio.DirectByteBuffer实例数,数量持续增长即存在泄漏风险 - 检查代码中是否手动调用
buffer.cleaner().clean()(不推荐,易出错),更应优先使用BufferPool或框架提供的池化机制(如 Netty 的PooledByteBufAllocator)
怎么预防和优化?
核心思路是“少分配、重利用、早释放”:
- 避免在循环或高频路径中反复调用
allocateDirect();改用ByteBuffer.wrap(byte[])(堆内)或池化 Buffer - 使用 Netty、Grizzly 等框架时,启用其内置的 direct buffer 池,并设置合理容量(如
maxOrder = 11对应最大 2MB chunk) - 若必须手动管理,确保 ByteBuffer 使用后尽快置为 null,并避免被长生命周期对象(static、ThreadLocal)意外持有
- 上线前压测时,监控 native memory 增长趋势;生产环境建议开启 NMT(Native Memory Tracking)并定期采样
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











