跳到正文
Elaine Blog
返回

Netty 内存池、引用计数与泄漏排查

网络与 I/O

Netty 用池化 ByteBuf 减少频繁分配和复制,但性能收益附带一个责任:谁保留缓冲区,谁就必须最终释放。引用计数错误会造成泄漏或提前释放。

Table of contents

Open Table of contents

ByteBuf 比 ByteBuffer 多了什么

ByteBuf 分开维护读索引和写索引,不必依赖 flip() 切换模式;它还能按需扩容、切片,并由分配器选择堆内或直接内存。 池化分配器复用大块内存中的区域,降低高频请求的分配开销。

池化不等于容量无限。长期持有小切片可能让背后的大缓冲区无法归还;写队列没有上限也会占满直接内存。

引用计数就是所有权账本

引用计数大于 0 表示内存仍有效。retain() 增加一个所有者,release() 放弃一个所有权;减到 0 后再访问会抛出 IllegalReferenceCountException

运行ByteBuf 生命周期样例下载

mvn -Dtest=NetworkServerLabTest#releasesReferenceCountedBuffers test

样例在 finally 中释放自己分配的缓冲区。入站 Handler 是否需要释放取决于基类和是否继续传递:

每次 retain 都必须能指出对应释放点。用“多 retain 一次保险”只会把崩溃改成泄漏。

切片和复制的区别

slice()duplicate() 通常共享底层存储,修改可能互相可见,生命周期也相互关联;copy() 创建独立内容,成本更高但所有权清晰。 跨异步边界时,要么明确保留共享缓冲区,要么复制成独立、不可变的业务对象。

泄漏排查顺序

  1. 区分 Java 堆、直接内存、线程栈、映射文件和本地库内存;
  2. 观察进程 RSS、直接缓冲区池、Netty 分配器和写队列指标;
  3. 在测试或灰度提高 Netty 泄漏检测级别,保存分配访问轨迹;
  4. 检查异常、取消、超时、连接关闭等非正常分支是否释放;
  5. 用最小复现验证修复,不要只靠扩大 MaxDirectMemorySize

Netty 官方的引用计数对象说明给出了入站、出站消息的所有权规则。 高检测级别会增加开销,生产启用前要评估采样策略。

下一步

阅读从 Netty 源码理解启动与读写链路,把这些组件放回实际调用主线。


分享这篇文章:

上一篇
Netty 线程模型与任务调度:不要阻塞 EventLoop
下一篇
从 Netty 源码理解启动与读写链路