记一次 Netty 堆外内存泄露排查过程( 三 )


文章插图
debug 松掉之后,内存立马飙升了!!这个时候我已经知道,这只臭虫飞不了多远了 。在 debug 的时候,挂起的是当前线程,那么肯定是当前线程某个地方申请了堆外内存,然后没有释放,接下来,快马加鞭,深入源码 。
每一次单步调试,我都会观察控制台的内存飙升的情况,很快,我们来到了这个地方

记一次 Netty 堆外内存泄露排查过程

文章插图
在这一行没执行之前,控制台的内存依然是 263B,然后,当执行完这一行之后,立马从 263B涨到519B(涨了256B)
记一次 Netty 堆外内存泄露排查过程

文章插图
于是,bug的范围进一步缩小,我将本次程序跑完,释然后客户端再来一次连接,断点打在 client.send这行,然后关闭客户端连接,之后直接进入到这个方法,随后的过程有点长,因为与 netty 的时间传播机制有关,这里就省略了,最后,我跟到了如下代码,handleWebsocket
记一次 Netty 堆外内存泄露排查过程

文章插图
在这个地方,我看了一处非常可疑的地方,在上图的断点上一行,调用 encoder分配了一段内存,调用完之后,我们的控制台立马就彪了 256B,所以,我怀疑肯定是这里申请的内存没有释放,他这里接下来调用encoder.encodePacket方法,猜想是把数据包的内容以二进制的方式写到这段 256B的内存,接下来,我跟到这段 encode 代码,单步执行之后,定位到这行代码
记一次 Netty 堆外内存泄露排查过程

文章插图
这段代码是把 packet 里面一个字段的值转换为一个 char,然而,当我使用 idea 预执行的时候,却抛出类一个愤怒的 NPE!!也就是说,框架申请到一段内存之后,在encoder的时候,自己GG了,自己给自己挖了个NPE的深坑,最后导致内存无法释放(最外层有堆外内存释放逻辑,现在无法执行到了),然后越攒越多,越攒越多,直到最后一根稻草,堆外内存就这样爆了,这里的源码有兴趣的读者可以自己去分析一下,限于篇幅原因,这里就不再分析了 。
 
阶段8:bug解决bug既然已经找到,接下来便要解决了,这里只需要解决这个NPE异常,就可以fix掉,我们的目标就是,让这个 subType字段不为空,我们先通过 idea 的线程调用栈定位到这个 packet 是在哪个地方定义的
记一次 Netty 堆外内存泄露排查过程

文章插图
我们找到 idea 的 debugger 面板,眼睛盯着 packet 这个对象不放,然后上线移动光标,便光速定位到,原来,定义 packet 对象这个地方在我们前面的代码其实已经出现过,我们查看了一下 subType这个字段,果然是,接下来,解决bug很容易 。
记一次 Netty 堆外内存泄露排查过程

文章插图
我们给这个字段赋值即可,由于这里是连接关闭事件,所以,我给他指定了一个名为 DISCONNECT 的字段(改日深入去研究socket.io的协议),反正这个bug是在连接关闭的时候触发的,就粗暴一点了 !== 。
解决这个bug的过程是:将这个框架的源码下载到本地,然后加上这一行,最后,我重新build一下,pom 里改改名字,推送到我们公司的仓库,这样,我项目就可以直接使用了 。
改完bug之后,习惯性地去github上找到引发这段bug的commit
记一次 Netty 堆外内存泄露排查过程

文章插图
好奇的是,为啥这位 dzncommiter 会写出这么一段如此明显的bug,而且时间就在今年3月30号,项目启动的前夕,真是无比尴尬呀
【记一次 Netty 堆外内存泄露排查过程】


推荐阅读