Bytebuf需要释放,否则可能导致OOM。
如果bytebbuf传递到了head或tail,不需要我们关心。
在head和tail里(head实现了outhandler、inhander。tail实现了inhander),底层自动调用了bytebuf.release。
其他情况需要我们手动释放。 注意simpleinboundHandler特殊
netty默认使用的直接内存(也叫非堆内存),它创建、销毁的开销同比堆内存大,所以用池化来复用。池化也能用在堆内存上,所以一共有四种:池化直接内存、池化堆内存、非池化直接内存、非池化堆内存。
会自动扩容,可扩容部分就是下图紫色。
创建ByteBuf
ByteBuf poolHeapBuf = ByteBufAllocator.DEFAULT.heapBuffer();ByteBuf poolDiredtBuf = ByteBufAllocator.DEFAULT.directBuffer();ByteBuf unpoolHeapBuf = Unpooled.buffer();ByteBuf unpoolDirBuf = Unpooled.directBuffer();System.out.println(poolHeapBuf);System.out.println(poolDiredtBuf);System.out.println(unpoolHeapBuf);System.out.println(unpoolDirBuf);
前两种是否是池化,需要看-Dio.netty.allocator.type=unpooled|pooled
,如果是pooled,则二者都是池化内存。
后两种一定是非池化内存。
参数为pooled时,输出如下。实际中,还可以用ChannelHandlerContext ctx来分配。
PooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 256)
PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 256)
UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 256)
UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(ridx: 0, widx: 0, cap: 256)
netty原码释放逻辑
当多个handler串起来时,数据会沿着链传统。用inboundhandler举例。
如何唤起下一个inhandler呢?可以调用fireChannelRead
class ServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ctx.fireChannelRead(msg);}
}
底层就是:
@Overridepublic ChannelHandlerContext fireChannelRead(final Object msg) {invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);return this;}
findContextInbound就是找到下一个handler。
invokeChannelRead就是调用下一个handler的invokeChannelRead,代码如下。
始终让Niothread去执行invokeChannelRead。并且执行的最后一个肯定是tail。
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);EventExecutor executor = next.executor(); // 该context所属的线程if (executor.inEventLoop()) {next.invokeChannelRead(m);} else {executor.execute(new Runnable() {@Overridepublic void run() {next.invokeChannelRead(m);}});}}
然后执行tail里面的代码
尤其是看看注释是啥:用户在channelRead里面没有处理message 并且该message传递到了tail节点,那么tail节点就会负责释放内存。
/*** Called once a message hit the end of the {@link ChannelPipeline} without been handled by the user* in {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)}. This method is responsible* to call {@link ReferenceCountUtil#release(Object)} on the given msg at some point.*/protected void onUnhandledInboundMessage(Object msg) {try {logger.debug("Discarded inbound message {} that reached at the tail of the pipeline. " +"Please check your pipeline configuration.", msg);} finally {ReferenceCountUtil.release(msg);}}
为什么始终让Niothread去执行invokeChannelRead?
假如我们这样做,Netty还是会交给NIO线程去释放。
new Thread(() -> {ctx.fireChannelRead(msg);}).start();
Netty 中的 invokeChannelRead 方法必须在 NIO 线程池中执行,以确保网络读事件的处理是高效、可靠和非阻塞的。
simpleinboundHandler
他为什么特殊?他在自己的finally快里面就释放了
@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {boolean release = true;try {if (acceptInboundMessage(msg)) {@SuppressWarnings("unchecked")I imsg = (I) msg;channelRead0(ctx, imsg);} else {release = false;ctx.fireChannelRead(msg);}} finally {if (autoRelease && release) {ReferenceCountUtil.release(msg);}}}
所以如果实现了sampleinboundhandler,byteBuf不用手动释放,也不用传递到tail去。
手动释放
调用release()
方法即可。
有特别注意的点。
ByteBuf的一些方法如slice()、duplicate、compositeByteBuf等,都是基于原数组的引用。
如果你释放掉了,其他人持有该引用写入就会发生问题。
最好是每调用上述方法一次,就引用次数+1retain()
,谁使用完,谁就release掉。
public static void main(String[] args) throws Exception {ByteBuf poolHeapBuf = ByteBufAllocator.DEFAULT.heapBuffer(10);// 模拟多人使用use(poolHeapBuf);use(poolHeapBuf);}private static void use(ByteBuf poolHeapBuf) {ByteBuf slice = poolHeapBuf.slice(0, 3);slice.retain();// 执行逻辑完,最后释放掉。slice.release();}
本文作者:WKP9418
本文地址:https://blog.csdn.net/qq_43179428/article/details/140564408