java直接内存是jvm堆外由操作系统分配的本地内存,通过bytebuffer.allocatedirect()调用unsafe.allocatememory()申请,不受gc管理且需-xx:maxdirectmemorysize显式限制,超限立即抛outofmemoryerror: direct buffer memory。

Java 直接内存(Direct Memory)是 JVM 堆外的一块本地内存,由操作系统直接分配,不经过堆管理,也不受 GC 自动回收。它的分配和限制机制与堆内存完全独立,必须单独配置和管控。
分配方式:绕过堆,直连系统
最常用的分配入口是 ByteBuffer.allocateDirect(size)。它内部调用 Unsafe.allocateMemory() 向操作系统申请连续内存,并返回一个地址指针;同时在 Java 堆中创建一个 DirectByteBuffer 对象,仅保存该地址和容量等元信息(约 24 字节),真正数据存在堆外。
- 分配瞬间完成,不触发 GC,也不受 -Xmx 控制
- 底层依赖 malloc/mmap 等系统调用,地址稳定,支持 DMA 和零拷贝(如 transferTo)
- 每个 DirectByteBuffer 默认关联一个 Cleaner(虚引用 + 引用队列),GC 发现对象不可达后,由 Cleaner 线程异步调用 freeMemory() 释放——这是被动回收,不是即时释放
限制参数:必须显式设上限
直接内存的硬性上限由 JVM 参数 -XX:MaxDirectMemorySize 控制。若未设置,JVM 默认将其设为与 -Xmx 相同的值(例如 -Xmx4g → 默认 MaxDirectMemorySize=4g),但这极易引发问题。
- 单位支持 k/K、m/M、g/G,例如:-XX:MaxDirectMemorySize=1g
- 设为 0 表示禁用直接内存(慎用,NIO/Netty 等会失败)
- 建议值为物理内存的 1/4~1/3,且确保 -Xmx + MaxDirectMemorySize ≤ 物理内存 × 80%(预留系统、线程栈、Metaspace 等开销)
- 该参数只起“熔断”作用:超限时立即抛出 java.lang.OutOfMemoryError: Direct buffer memory,不等待 GC,也不影响堆内存
关键检查点:分配前就做限额校验
每次调用 allocateDirect 时,JVM 都会通过 Bits.reserveMemory() 检查当前已分配总量是否超过 MaxDirectMemorySize。一旦超限,立刻拒绝分配并抛异常,不会等到内存真正耗尽才报错。
- 这个检查发生在用户代码调用 allocateDirect 的那一刻,是同步、确定性的
- 因此,OOM 报错位置通常就在 allocateDirect() 调用行,堆栈里明确含 “Direct buffer memory” 字样
- 它和堆 OOM(Java heap space)完全无关,调大 -Xmx 对 Direct OOM 无效
监控与验证:不能只靠参数
配了参数不等于万事大吉。需配合运行时工具确认实际用量和回收行为:
- 启动加 -XX:NativeMemoryTracking=summary,运行中执行 jcmd
VM.native_memory summary 查看 direct 区用量 - 通过 JMX 访问 java.nio:type=BufferPool,name=direct,监控 MemoryUsed / TotalCapacity 变化趋势
- 压测后加 -XX:+PrintDirectMemoryDetails,JVM 退出时打印分配/释放统计,辅助定位泄漏点










