管好DirectByteBuffer需控分配、盯释放、看得见增长:它不被GC主动回收,须显式release;设MaxDirectMemorySize并监控usedDirectMemory;用leakDetectionLevel和Arthas定位泄漏。

管好 DirectByteBuffer 的核心就三点:**控住分配、盯住释放、看得见增长**。它不是堆内对象,GC 不会主动回收底层内存,靠的是引用计数(Netty)或 Cleaner 异步清理(原生 ByteBuffer),一旦漏掉 release 或强引用没断,内存就卡在那儿不动了。
明确生命周期,谁申请谁负责释放
DirectByteBuffer 本身在堆上,但持有的内存不在堆里。你不能指望 GC 来“顺手”把它清掉——尤其是 Netty 的 PooledByteBuf,必须显式调用 release() 才能归还池中内存。
- 所有从
PooledByteBufAllocator获取的ByteBuf,必须在 finally 块或 try-with-resources 中调用release() - 避免在长生命周期对象(如单例 handler、静态 map)里长期持有 ByteBuf 引用,哪怕只存一个 buffer,也会锁住整块堆外内存
- 不要依赖
Cleaner自动回收:它依赖 GC 触发,有延迟;若 GC 不频繁或对象被强引用,Cleaner 根本不执行
设上限 + 开监控,不让它偷偷涨
默认 -XX:MaxDirectMemorySize 等于 -Xmx,容易误判。建议显式设值(比如 -XX:MaxDirectMemorySize=512m),再配上实时指标盯住实际用量。
- 用
PlatformDependent.usedDirectMemory()(Netty 4.1.90+)读取当前已用直接内存,暴露为 Prometheus 指标 - JMX 查
java.nio:type=BufferPool,name=direct的MemoryUsed和Count,比堆内监控更及时 - 写个定时任务每 5 秒打一次日志或上报,趋势异常立刻告警
用工具链快速定位泄漏点
等 RSS 涨到 OOM 再查就晚了。日常要组合使用轻量级手段提前发现苗头:
立即学习“Java免费学习笔记(深入)”;
- 启动加参数:
-Dio.netty.leakDetectionLevel=paranoid,泄漏时直接打出创建栈,精确到 handler 行号 - Arthas 实时查:
vmtool --action getInstances --className java.nio.DirectByteBuffer --express 'instances.length',看数量是否远超连接数 - 怀疑时跑快照对比:
jcmd <pid> VM.native_memory detail.diff,重点看Direct buffer和Internal段增长路径
别让框架替你背锅,先看代码怎么用
很多泄漏不是 Netty 或 NIO 的问题,而是业务层绕过池化、滥用 allocateDirect,或者在异常分支里忘了 release。
- 搜代码里所有
allocateDirect调用,确认都有对应 cleanup 逻辑(包括 catch 块) - 检查自定义
ChannelHandler:有没有在channelRead0里拿了 buf 没放,或在write后没调flush导致 buf 堆积 - 排查
MappedByteBuffer和Unsafe.allocateMemory—— 它们也走同一套堆外内存机制,但更容易被忽略


















