用途:解释“堆没满但内存吃紧”“RSS 远高于 Xmx”这类问题
- 避免复制:与内核态/本地库交互时,堆内存需先拷贝到本地缓冲;直接缓冲可省一次拷贝(网络 IO、文件映射)。
- 不受 GC 停顿影响:生命周期由显式或引用队列管理,适合大容量池化内存。
- 突破堆限制:某些场景需要超大连续内存。
代价:不受堆大小约束、GC 无法及时回收、排障不可见(堆 dump 里没有)、分配与释放错误会直接体现为进程内存增长。
| 形态 |
管理者 |
释放时机 |
风险 |
ByteBuffer.allocateDirect |
JVM 的 Bits 计数器 + Cleaner |
依赖 GC 触发清理 |
分配快于清理时会 OOM 或堆积 |
Netty PooledByteBufAllocator |
池化 arena/chunk,引用计数 |
release() 归零 |
忘记 release 造成泄漏(可用泄漏检测档) |
Unsafe/JNI/MMap |
手工 |
手工 free |
最难排查,需要专项审计 |
-XX:MaxDirectMemorySize 限制 allocateDirect 总量(默认约等于 -Xmx,容易与堆叠加超出容器限制)。
- 观测:
BufferPool MBean(direct 的 count/used capacity)、Netty 的池指标、jcmd VM.native_memory summary(需开 NMT)。
- 容器内务必把“堆 + 元空间 + 线程栈 + 堆外 + 代码缓存”总和与容器限制对齐,留余量。
| 症状 |
原因 |
处理 |
Direct buffer memory OOM |
分配速率高于 Cleaner 回收 |
复用 buffer、显式限流、必要时手动 clean(谨慎)、降低单次大小 |
| RSS 持续高于 Xmx 且稳定 |
正常池化占用或 glibc arena |
NMT 拆解;必要时限制 arena 或换分配器 |
| RSS 持续上涨 |
本地内存泄漏 |
审计 Unsafe/JNI/MMap 配对释放;Netty 开泄漏检测定位调用栈 |
| GC 后仍不释放 |
Cleaner 依赖 GC 触发,老年代回收不频繁 |
显式释放或限制池大小 |
- 需要堆外就集中封装(一个内存管理组件负责申请/释放/指标),禁止散落
Unsafe。
- 有引用计数就要有“归零断言”与泄漏检测(Netty
-Dio.netty.leakDetection.level=paranoid 仅用于定位)。
- 池大小、单块大小、等待策略都要有指标与告警。
- 与容器内存规划一起设计,而不是等 OOMKilled 再查。
| 误区 |
修正 |
不设 MaxDirectMemorySize |
堆外与堆叠加会撑爆容器 |
| 认为 GC 会及时回收堆外 |
回收依赖 Cleaner 触发,可能滞后 |
散落使用 Unsafe |
集中封装并配对释放 |
| 只看堆指标判断内存健康 |
必须同时看 RSS、NMT、BufferPool |
- Netty 官方文档:Pooled ByteBufAllocator 与 Leak Awareness
- OpenJDK
java.nio.Bits 与 DirectByteBuffer 实现说明
- Nitsan Wakart《Off-heap memory in the JVM》相关公开文章