Netty writeAndFlush操作引发内存泄漏问题排查求助
问题场景
开发基于Netty的类SignalR Java包,压测时发现内存泄漏,定位到回写客户端结果的writeMessageInternal方法:调用channel.writeAndFlush(buf)时出现泄漏,注释该行则无泄漏。buf是Unpooled ByteBuf,环境为Java 21 + netty-all 4.1.58.Final。
泄漏代码:
private ChannelFuture writeMessageInternal(Message message) { ByteBuf buf; Codec codec = message.getCodec(); try { buf = codec.encode(message); } catch (Exception e) { throw new RuntimeException(); } ChannelFuture channelFuture = channel.writeAndFlush(buf); return channelFuture; }
可能的原因及排查方案
1. ChannelFuture被长期持有导致ByteBuf无法回收
Netty在调用writeAndFlush后,会在IO操作完成后自动释放Unpooled ByteBuf,但如果上层代码长期持有返回的ChannelFuture(比如存入静态集合、作为长生命周期对象的成员变量),ChannelFuture会关联ByteBuf的引用,导致GC无法回收。
排查&解决:
- 检查上层调用
writeMessageInternal的代码,确认是否长期保留了ChannelFuture实例。 - 可以给
ChannelFuture添加完成监听器,主动确认ByteBuf状态,即使自动释放失效也能手动兜底:
channelFuture.addListener(future -> { if (!buf.isReleased()) { buf.release(); // 打印日志记录未释放情况,便于排查 } });
2. Netty版本与Java 21的兼容性问题
Netty 4.1.58.Final发布于2021年,而Java 21是2023年推出的版本,两者可能存在内存管理(如ZGC/Shenandoah GC)的兼容性bug,导致ByteBuf的自动释放逻辑异常。
排查&解决:
- 升级Netty到4.1.x分支的最新稳定版(如4.1.100.Final及以上),这些版本修复了大量高版本Java的适配问题。
3. Codec编码逻辑中存在ByteBuf的额外引用
检查codec.encode(message)方法,确认生成的Unpooled ByteBuf没有被存入全局缓存、Message对象的成员变量等长生命周期容器中。如果ByteBuf被这些对象持有,即使writeAndFlush完成,也无法被GC回收。
排查&解决:
- 在
encode方法结束后打印ByteBuf的引用计数:System.out.println(buf.refCnt()); - 在
ChannelFuture完成的监听器中再次打印引用计数,确认是否降到0。如果未降到0,跟踪引用来源。
4. 启用Netty内存泄漏检测工具
Netty自带内存泄漏检测机制,开启后能精准定位泄漏点:
- 添加JVM启动参数:
-Dio.netty.leakDetection.level=PARANOID
该参数会跟踪每个ByteBuf的分配和释放路径,一旦发现泄漏,会打印完整的堆栈信息,直接指向持有ByteBuf引用的代码位置。
内容的提问来源于stack exchange,提问作者Arshiya Kiani

