当Tomcat遇上Netty

故事背景

嘀~,生产事故,内存泄漏!

昨天下午,突然收到运维的消息,分部某系统生产环境内存泄漏了,帮忙排查一下。

排查过程

第一步,要日志

分部给到的异常日志大概是这样(鉴于公司规定禁止截图禁止拍照禁止外传任何信息,下面是我网上找到一张类似的报错):

LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.

Recent access records: 

#1: 
  io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:273)
  io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253)
  io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
  io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
	io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
	io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
	io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:646)
	io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:581)
	io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:498)
	io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:460)
	io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
	java.lang.Thread.run(Thread.java:748)

这一看,不得了了,ByteBuf没有释放,导致内存泄漏了。

第二步,看内存指标

既然知道了是内存泄漏,赶紧让运维看下内存使用情况,特别是堆外内存使用情况(因为用了Netty),根据运维反馈,堆内内存使用正常,堆外内存居高不下。

OK,到这里已经可以很明确地断言:堆外内存泄漏了。

此时,分两步走,一步是把gateway换成zuul压测观察,一步是内存泄漏问题排查。

第三步,要代码

让分部这个项目的负责人把代码给到我,我打开一看,傻眼了,就一个简单的Spring Cloud Gateway项目,里面还包含了两个类,一个是AuthFilter用来做权限校验的,一个是XssFilter用来防攻击的。

Spring Cloud Gateway使用的是Netty,zuul 1.x使用的是Tomcat,本文来源于工纵耗彤哥读源码。

第四步,初步怀疑

快速扫一下各个类的代码,在XssFilter里面看到了跟ByteBuf相关的代码,但是,没有明显地ByteBuf没有释放的信息,很简单,先把这个类屏蔽掉,看看还有没有内存泄漏。

但是,怎么检测有没有内存泄漏呢?总不能把这个类删掉,在生产上跑吧。

第五步,参数及监控改造

其实,很简单,看过Netty源码的同学,应该比较清楚,Netty默认使用的是池化的直接内存实现的ByteBuf,即PooledDirectByteBuf,所以,为了调试,首先,要把池化这个功能关闭。

直接内存,即堆外内存。

为什么要关闭池化功能?

因为池化是对内存的一种缓存,它一次分配16M内存且不会立即释放,开启池化后不便观察,除非慢慢调试。

那么,怎么关闭池化功能呢?

在Netty中,所有的ByteBuf都是通过一个叫作ByteBufAllocator来创建的,在接口ByteBufAllocator中有一个默认的分配器,找到这个默认的分配器,再找到它创建的地方,就可以看到相关的代码了。

public interface ByteBufAllocator {
   

    ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;
}
public final class ByteBufUtil {
   

    static final ByteBufAllocator DEFAULT_ALLOCATOR;

    static {
   
        // 本文来源于工纵耗彤哥读源码
        String allocType = SystemPropertyUtil.get(
                "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
        allocType = allocType.toLowerCase(Locale.US).trim();

        ByteBufAllocator alloc;
        if ("unpooled".equals(allocType)) {
   
            alloc = UnpooledByteBufAllocator.DEFAULT
  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值