Netty4优化:将ByteBuf指定字节直接复制到Native Direct ByteBuffer
Netty 4 直接将ByteBuf数据写入Native Direct ByteBuffer的优化方案
核心优化思路
跳过堆ByteBuffer中转环节,直接在Netty的ByteBuf与目标Native Direct ByteBuffer之间完成内存拷贝,利用Netty原生API或底层内存操作实现高效数据传输。
方案一:通过Buffer边界控制+Netty原生API实现直接写入
你之前调用readBytes(bb)触发异常,本质是未正确设置Direct ByteBuffer的读写边界。只需先限定目标缓冲区的写入范围,再调用Netty的readBytes(ByteBuffer)方法即可:
try { receivingBuf.writeBytes((ByteBuf) msg); if (receivingBuf.readableBytes() >= nextPacketLength) { // 修改ByteBufferPoolManager,使其返回Native Direct ByteBuffer(而非堆ByteBuffer) ByteBuffer directBb = ByteBufferPoolManager.getDirectByteBuffer(); int targetLength = nextPacketLength - 12; // 限定Direct ByteBuffer的写入边界 directBb.position(0); directBb.limit(targetLength); // 直接从ByteBuf写入指定长度的数据到Direct ByteBuffer receivingBuf.readBytes(directBb); // 重置缓冲区指针供后续处理 directBb.flip(); MyCustomMethodToProcessData(directBb); } } finally { ((ByteBuf) msg).release(); }
关键:将ByteBuffer池的存储对象改为Native Direct ByteBuffer,彻底消除堆缓冲区的中转拷贝。
方案二:底层内存地址拷贝(高性能但需谨慎使用)
若receivingBuf是Netty的DirectByteBuf(NioSocketChannel默认接收缓冲区通常为Direct类型),可借助Unsafe API直接操作内存地址完成拷贝,完全规避JVM层面的数据复制:
import sun.misc.Unsafe; import java.lang.reflect.Field; // 提前通过反射获取Unsafe实例(JDK9+需配置模块访问权限) private static final Unsafe UNSAFE; static { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); UNSAFE = (Unsafe) field.get(null); } catch (Exception e) { throw new RuntimeException(e); } } // ChannelRead逻辑实现 try { receivingBuf.writeBytes((ByteBuf) msg); if (receivingBuf.readableBytes() >= nextPacketLength) { ByteBuffer directBb = ByteBufferPoolManager.getDirectByteBuffer(); int targetLength = nextPacketLength - 12; // 获取Netty DirectByteBuf的内存起始地址+当前读指针偏移 long srcAddr = ((DirectByteBuf) receivingBuf).memoryAddress() + receivingBuf.readerIndex(); // 获取目标Native Direct ByteBuffer的内存起始地址 long dstAddr = UNSAFE.addressOf(directBb); // 直接执行内存拷贝 UNSAFE.copyMemory(null, srcAddr, null, dstAddr, targetLength); // 更新ByteBuf的读指针位置 receivingBuf.readerIndex(receivingBuf.readerIndex() + targetLength); // 重置目标缓冲区指针 directBb.position(0); directBb.limit(targetLength); MyCustomMethodToProcessData(directBb); } } finally { ((ByteBuf) msg).release(); }
注意:该方案依赖Unsafe私有API,不同JDK版本存在兼容性风险;若receivingBuf为HeapByteBuf,需先转为DirectByteBuf再操作(可通过receivingBuf.direct()判断类型)。
方案三:Channel专属Direct缓冲区中转(次优但易实现)
若暂时无法修改ByteBuffer池的实现,可为每个Channel分配一个固定大小的Direct ByteBuffer作为专属中转,仅在首次初始化时分配,后续复用:
import io.netty.util.AttributeKey; // 在ChannelHandler的handlerAdded方法中初始化专属缓冲区 @Override public void handlerAdded(ChannelHandlerContext ctx) { // 根据业务最大包长度分配Direct ByteBuffer ByteBuffer channelDirectBuf = ByteBuffer.allocateDirect(maxPacketLength).order(ByteOrder.nativeOrder()); ctx.channel().attr(AttributeKey.valueOf("CHANNEL_DIRECT_BUF")).set(channelDirectBuf); } // ChannelRead逻辑实现 try { receivingBuf.writeBytes((ByteBuf) msg); if (receivingBuf.readableBytes() >= nextPacketLength) { int targetLength = nextPacketLength - 12; // 获取Channel专属Direct中转缓冲区 ByteBuffer channelDirectBuf = ctx.channel().attr(AttributeKey.valueOf("CHANNEL_DIRECT_BUF")).get(); channelDirectBuf.position(0); channelDirectBuf.limit(targetLength); receivingBuf.readBytes(channelDirectBuf); channelDirectBuf.flip(); // 将Direct缓冲区的数据写入目标Native Direct ByteBuffer(Direct到Direct的拷贝性能远高于堆到Direct) ByteBuffer targetBb = ByteBufferPoolManager.getDirectByteBuffer(); targetBb.position(0); targetBb.limit(targetLength); targetBb.put(channelDirectBuf); targetBb.flip(); MyCustomMethodToProcessData(targetBb); } } finally { ((ByteBuf) msg).release(); }
内容的提问来源于stack exchange,提问作者Ralms
相关产品推荐
相关产品推荐

