Netty内存分配时直接内存(DirectBuffer)的源码分析

说明:针对线上发生的一次直接内存OOM,通过源码了解一下 Netty 的直接内存使用,主要看 Server 端。

一、环境

  • 系统:macOS Monterey(MacBook Pro 13-inch,M1,2020)
  • JDK:11
  • Maven:3.8.1
  • IDEA:2022.2.1(Ultimate Edition)
  • Netty:netty-all 4.1.65.Final

二、工程

1、引入依赖

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.65.Final</version>
        </dependency>

2、创建Server、Client

创建4个类:NettyServer、NettyServerHandler、NettyClient、NettyClientHandler,Server、Client端的代码比较简单,Server端的 pipeline里添加了一个编码器、一个业务Handler。

pipeline.addLast(new StringEncoder());
pipeline.addLast(new NettyServerHandler());

三、源码分析

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    String msg = "12345678901234567890123456789012345678901234567890,";

    Logger log = LoggerFactory.getLogger(NettyServerHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("连接建立...");
        log.warn("发送数据...");
        for (int i = 0; i < 10000; i++) {
            ctx.writeAndFlush(msg);		// 从这里开始debug
        }
        log.warn("发送数据结束...");
    }
  
// ChannelHandlerContext 实现类是 AbstractChannelHandlerContext,
// 继续下一步到:AbstractChannelHandlerContext#writeAndFlush(Object)
    @Override
    public ChannelFuture writeAndFlush(Object msg) {
        return writeAndFlush(msg, newPromise());
    }
  
 // 继续下一步:AbstractChannelHandlerContext#write(Object, boolean, ChannelPromise)
      private void write(Object msg, boolean flush, ChannelPromise promise) {
        ObjectUtil.checkNotNull(msg, "msg");
        try {
            if (isNotValidPromise(promise, true)) {
                ReferenceCountUtil.release(msg);
                // cancelled
                return;
            }
        } catch (RuntimeException e) {
            ReferenceCountUtil.release(msg);
            throw e;
        }

        final AbstractChannelHandlerContext next = findContextOutbound(flush ?
                (MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);	// flash为true,断点到这一行
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
          

// 继续下一步        
     void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
        if (invokeHandler()) {
            invokeWrite0(msg, promise);		// 断点到这一行
            invokeFlush0();
        } else {
            writeAndFlush(msg, promise);
        }
    }
          
// 继续下一步
    private void invokeWrite0(Object msg, ChannelPromise promise) {
        try {
            ((ChannelOutboundHandler) handler()).write(this, msg, promise);		// 断点到这一行
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    }   
          
// 继续下一步 pipeline配置的第一个ChannelOutboundHandler是StringEncoder, 此处断点进入到 MessageToMessageEncoder#write
          public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            CodecOutputList out = null;
            try {
              if (acceptOutboundMessage(msg)) {
                out = CodecOutputList.newInstance();
                @SuppressWarnings("unchecked")
                I cast = (I) msg;
                try {
                  encode(ctx, cast, out);		// 断点到这一行
                } finally {
                  ReferenceCountUtil.release(cast);
                }
                
// 继续下一步
    @Override
    protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
        if (msg.length() == 0) {
            return;
        }
				// ctx.alloc() 的值是:PooledByteBufAllocator(directByDefault: true)
        out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset));	// 断点到这一行
    }
                
// 继续下一步 ByteBufUtil.encodeString
    public static ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset) {
        return encodeString0(alloc, false, src, charset, 0);
    }
                
// 继续下一步
    static ByteBuf encodeString0(ByteBufAllocator alloc, boolean enforceHeap, CharBuffer src, Charset charset,
                                 int extraCapacity) {
        final CharsetEncoder encoder = CharsetUtil.encoder(charset);
        int length = (int) ((double) src.remaining() * encoder.maxBytesPerChar()) + extraCapacity;
        boolean release = true;
        final ByteBuf dst;
        if (enforceHeap) {
            dst = alloc.heapBuffer(length);
        } else {
        // 断点到这一行。因为 enforceHeap 的值是 false,如果是 true,将分配堆内存,else 猜测是非堆内存(直接内存),继续往下验证...
            dst = alloc.buffer(length);
        }
      
// 继续下一步 AbstractByteBufAllocator#buffer(int)
    @Override
    public ByteBuf buffer(int initialCapacity) {
        if (directByDefault) {
            return directBuffer(initialCapacity);	// 断点到这一行,
        }
        return heapBuffer(initialCapacity);
    }
      
// 继续下一步
    @Override
    public ByteBuf directBuffer(int initialCapacity) {
        return directBuffer(initialCapacity, DEFAULT_MAX_CAPACITY);
    }
      
// 继续下一步
    @Override
    public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
        if (initialCapacity == 0 && maxCapacity == 0) {
            return emptyBuf;
        }
        validate(initialCapacity, maxCapacity);
        return newDirectBuffer(initialCapacity, maxCapacity);	// 断点到这一行,已经看到使用的是直接内存了
    }
      
// 继续下一步 PooledByteBufAllocator#newDirectBuffer,为什么是这个实现?在上面 ctx.alloc() 中已得到,
    @Override
    protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
        PoolThreadCache cache = threadCache.get();
        PoolArena<ByteBuffer> directArena = cache.directArena;

        final ByteBuf buf;
        if (directArena != null) {
        	// 断点到这一行,已基本明确是使用直接内存。
        	// 关于 directArena 对象,则是Netty内存分配的相关大小及数据等的划分,此篇只分析是否使用直接内存
            buf = directArena.allocate(cache, initialCapacity, maxCapacity);
        } else {
            buf = PlatformDependent.hasUnsafe() ?
                    UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) :
                    new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
        }

        return toLeakAwareBuffer(buf);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值