Arthas 偶现 ByteBuf LEAK
背景
上一篇文章中,我们遇到了YGC的问题,排查过程中使用了阿里的arthas,排查过程中我们在arthas的日志中偶然发现了一些有意思的日志:
1 | 01 2019-05-10 11:21:00.968 ERROR [nioEventLoopGroup-2-2:i.n.u.ResourceLeakDetector] LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information. |
显示有泄露。当时这个问题只是记录了一下,本周开始排查。
排查过程
Netty
有泄露级别,我们可以通过调整泄露级别来追踪我们每个ByteBuf
的申请和释放情况。这里把我们的级别调整为最高,追踪每一个ByteBuf
,记录每一个使用过程。
压测过后发现很奇怪的是,我们的log中并没有搜索到LEAK
的内容。考虑到我们使用了arthas,所以开启arthas随便抓了几个类的调用情况,重新看log,还是没发现任何异常的地方。
系统log中没有,再看下arthas的日志,发现果然有问题:
1 | 2019-05-14 11:31:52.545 ERROR [nioEventLoopGroup-2-4:i.n.u.ResourceLeakDetector] LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information. |
由于Netty泄露检测是全局参数,所以也把arthas内置的Netty的泄露检测开启了,这里我们发现一个奇怪的类:io.termd.core.telnet.netty.TelnetChannelHandler
,这个并不是Netty自己的类,google一把发现是alibaba/termd中的一个类,看下代码:
1 | public void channelRead(ChannelHandlerContext ctx, Object msg) { |
channelRead
里,分配的ByteBuf
既没有释放,也没有在Pipeline
上继续传递。导致这个ByteBuf
申请之后没有release
。
修改一下:
1 | public void channelRead(ChannelHandlerContext ctx, Object msg) { |
排查过程中收获的一些知识点
- Netty默认使用的是池化内存,且是直接内存。如果是
android
系统,默认使用的是非池化的ByteBuf
。 TelnetChannelHandler
继承自ChannelInboundHandlerAdapter
,这个类的是不会自动释放ByteBuf
的,而SimpleChannelInboundHandler
是自动释放ByteBuf
的,继承SimpleChannelInboundHandler
需要实现channelRead0
方法,看下SimpleChannelInboundHandler
的代码:
1 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { |
被处理过的msg都会在finally
里释放(默认autoRelease = true
)。
- Netty自带的编解码器,是会自动释放
ByteBuf
的,比如MessageToByteEncoder
和LengthFieldBasedFrameDecoder
。 Pipeline
的尾节点TailContext
也会释放ByteBuf
,有些需要释放的ByteBuf
我们也可以通过fireChannelRead
让它继续走到Pipeline
的尾节点中。