Netty之ByteBuf介绍(三)

文章详细介绍了Netty中的ByteBuf内存管理,包括堆外内存的释放和引用计数法的内存回收机制。此外,还讨论了零拷贝技术,如slice、duplicate和CompositeByteBuf的使用,以及Unpooled工具类的相关操作。最后提到了ByteBuf的深拷贝方法copy。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

11.Netty之ByteBuf介绍(三)

一、内存回收(retain & release)

由于 Netty 中有堆外内存(直接内存)的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。

  • UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可
  • UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
  • PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存

Netty 这里采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口

  • 每个 ByteBuf 对象的初始计数为 1
  • 调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收
  • 调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收
  • 当计数为 0 时,底层内存会被回收,这时即使 ByteBuf 对象还在,其各个方法均无法正常使用

内存回收规则

因为 pipeline 的存在,一般需要将 ByteBuf 传递给下一个 ChannelHandler,如果在每个 ChannelHandler 中都去调用 release ,就失去了传递性(如果在这个 ChannelHandler 内这个 ByteBuf 已完成了它的使命,那么便无须再传递)

基本规则是,谁是最后使用者,谁负责 release

  • 起点,对于 NIO 实现来讲,在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read 方法中首次创建 ByteBuf 放入 pipeline(line 163 pipeline.fireChannelRead(byteBuf))
  • 入站 ByteBuf 处理原则
    • 对原始 ByteBuf 不做处理,调用 ctx.fireChannelRead(msg) 向后传递,这时无须 release
    • 将原始 ByteBuf 转换为其它类型的 Java 对象,这时 ByteBuf 就没用了,必须 release
    • 如果不调用 ctx.fireChannelRead(msg) 向后传递,那么也必须 release
    • 注意各种异常,如果 ByteBuf 没有成功传递到下一个 ChannelHandler,必须 release
    • 假设消息一直向后传,那么 TailContext 会负责释放未处理消息(原始的 ByteBuf)
  • 出站 ByteBuf 处理原则
    • 出站消息最终都会转为 ByteBuf 输出,一直向前传,由 HeadContext flush 后 release
  • 异常处理原则
    • 有时候不清楚 ByteBuf 被引用了多少次,但又必须彻底释放,可以循环调用 release 直到返回 true

TailContext 释放未处理消息逻辑

// io.netty.channel.DefaultChannelPipeline#onUnhandledInboundMessage(java.lang.Object)
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);
    }
}

当ByteBuf被传到了pipeline的head与tail时,ByteBuf会被其中的方法彻底释放,但前提是ByteBuf被传递到了head与tail中

具体代码

// io.netty.util.ReferenceCountUtil#release(java.lang.Object)
public static boolean release(Object msg) {
    if (msg instanceof ReferenceCounted) {
        return ((ReferenceCounted) msg).release();
    }
    return false;
}

二、零拷贝

2.1 slice

【零拷贝】的体现之一,对原始 ByteBuf 进行切片成多个 ByteBuf,切片后的 ByteBuf 并没有发生内存复制,还是使用原始 ByteBuf 的内存,切片后的 ByteBuf 维护独立的 read,write 指针,修改子分片,会修改原ByteBuf。

11.Netty之ByteBuf介绍(三)01.png

示例代码

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;


public class ByteBufSpliceDemo {
    public static void main(String[] args) {
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
        byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});

        System.out.println("------byteBuf------");
        printBuf(byteBuf);

        //分片1
        ByteBuf slice1 = byteBuf.slice(0,5);
        System.out.println("------slice1------");
        printBuf(slice1);

        //分片2
        ByteBuf slice2 = byteBuf.slice(5, 5);
        System.out.println("------slice2------");
        printBuf(slice2);

        //将最后一位0修改成10
        slice2.setByte(4,10);
        System.out.println("------修改后slice2------");
        printBuf(slice2);

        //打印修改后的byteBuf
        System.out.println("------修改后byteBuf------");
        printBuf(byteBuf);
    }

    static void printBuf(ByteBuf byteBuf){
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i< byteBuf.writerIndex();i++) {
            stringBuilder.append(byteBuf.getByte(i));
        }
        System.out.println(stringBuilder);
    }
}

运行结果

------byteBuf------
1234567890
------slice1------
12345
------slice2------
67890
------修改后slice2------
678910
------修改后byteBuf------
12345678910

注意:slice后的分片,不能再次写入新的数据,这会影响原ByteBuf。

2.2 duplicate

【零拷贝】的体现之一,就好比截取了原始 ByteBuf 所有内容,并且没有 max capacity 的限制,也是与原始 ByteBuf 使用同一块底层内存,只是读写指针是独立的。

11.Netty之ByteBuf介绍(三)02.png

示例代码

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

import static com.lilinchao.netty.bytebuf.ByteBufSpliceDemo.printBuf;

public class ByteBufDuplicateDemo {
    public static void main(String[] args) {
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
        byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});

        //拷贝一块buf
        ByteBuf duplicate = byteBuf.duplicate();
        printBuf(duplicate);

        //将最后一位0修改成10
        duplicate.setByte(9,10);
        //打印byteBuf
        printBuf(byteBuf);

        // 写入新数据11
        duplicate.writeByte(11);
        //打印byteBuf
        printBuf(byteBuf);
    }

}

运行结果

1234567890
12345678910
12345678910
2.3 CompositeByteBuf

【零拷贝】的体现之一,可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免拷贝。

示例代码

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;

import static com.lilinchao.netty.bytebuf.ByteBufSpliceDemo.printBuf;


public class ByteBufCompositeDemo {
    public static void main(String[] args) {
        ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.buffer(5);
        byteBuf1.writeBytes(new byte[]{1, 2, 3, 4, 5});

        ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.buffer(5);
        byteBuf2.writeBytes(new byte[]{6, 7, 8, 9, 0});

        CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
        // 组合两个byteBuf
        // true 表示增加新的 ByteBuf 自动递增 write index, 否则 write index 会始终为 0
        compositeByteBuf.addComponents(true,byteBuf1, byteBuf2);

        printBuf(compositeByteBuf);
    }
}

运行结果

1234567890

CompositeByteBuf 是一个组合的 ByteBuf,它内部维护了一个 Component 数组,每个 Component 管理一个 ByteBuf,记录了这个 ByteBuf 相对于整体偏移量等信息,代表着整体中某一段的数据。

  • 优点,对外是一个虚拟视图,组合这些 ByteBuf 不会产生内存复制
  • 缺点,复杂了很多,多次操作会带来性能的损耗
2.4 Unpooled

Unpooled 是一个工具类,类如其名,提供了非池化的 ByteBuf 创建、组合、复制等操作

这里仅介绍其跟【零拷贝】相关的 wrappedBuffer 方法,可以用来包装 ByteBuf。

示例代码

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;

import static com.lilinchao.netty.bytebuf.ByteBufSpliceDemo.printBuf;


public class ByteBufCompositeDemo {
    public static void main(String[] args) {
        ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.buffer(5);
        byteBuf1.writeBytes(new byte[]{1, 2, 3, 4, 5});

        ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.buffer(5);
        byteBuf2.writeBytes(new byte[]{6, 7, 8, 9, 0});

//        CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
//        // 组合两个byteBuf,主要要使用带有increaseWriteIndex的,否则会失败。
//        compositeByteBuf.addComponents(true,byteBuf1, byteBuf2);
        // 组合两个byteBuf,底层使用CompositeByteBuf。
        ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(byteBuf1, byteBuf2);

        printBuf(wrappedBuffer);
    }
}

三、深度拷贝

ByteBuf提供了copy方法,这一类方法是真正的拷贝原ByteBuf到新的内存,返回一个新的ByteBuf,与原ByteBuf没有关系。

提供两个拷贝:

  • 一个是全量
  • 一个指定位置和长度
public abstract ByteBuf copy();

public abstract ByteBuf copy(int index, int length);

示例代码

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

import static com.lilinchao.netty.bytebuf.ByteBufSpliceDemo.printBuf;

public class ByteBufCopyDemo {
    public static void main(String[] args) {
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
        byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});

        ByteBuf copy1 = byteBuf.copy();
        printBuf(copy1);

        ByteBuf copy2 = byteBuf.copy(5, 5);
        printBuf(copy2);
    }
}

运行结果

1234567890
67890
locator.DEFAULT.buffer(10);
        byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});

        ByteBuf copy1 = byteBuf.copy();
        printBuf(copy1);

        ByteBuf copy2 = byteBuf.copy(5, 5);
        printBuf(copy2);
    }
}

运行结果

1234567890
67890
### NettyByteBuf 的用法及常见问题解析 #### 1. ByteBuf 的基本概念 ByteBufNetty 提供的一种高效缓冲区管理工具,用于替代 JDK 自带的 ByteBuffer。它具有更灵活的功能设计和更高的性能表现[^1]。 #### 2. 创建 ByteBuf 实例 以下是创建 ByteBuf 的简单示例代码: ```java public class NettyByteBufExample { public static void main(String[] args) { // 默认分配器创建 ByteBuf 对象 ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(); System.out.println(buf); } } ``` 上述代码展示了如何通过 `ByteBufAllocator` 来获取默认的 ByteBuf 实例[^2]。 #### 3. ByteBuf 的存储结构 ByteBuf 的内部存储结构分为个主要部分:读索引 (`readerIndex`)、写索引 (`writerIndex`) 和容量 (`capacity`)。这些字段共同决定了数据的操作范围和边界。 #### 4. 控制池化功能 Netty 支持通过环境变量配置是否启用内存池机制。如果希望禁用内存池,则可以通过设置如下参数实现: ```properties -Dio.netty.allocator.type=unpooled ``` 反之,若要启用内存池,则将值设为 `pooled` 即可。 #### 5. 高度可扩展性 ByteBuf 接口允许开发者根据实际需求对其进行扩展或自定义实现。例如,在某些特殊场景下可能需要增加额外的方法或属性支持,此时可通过继承现有类或者重新实现接口完成定制开发工作[^4]。 #### 6. 内存泄漏检测 为了防止因不当操作而导致资源未释放而引发的潜在风险,Netty 设计了一套完善的内存泄露监控体系。当发现异常情况时会自动记录相关信息以便排查问题所在位置[^3]。 --- ### 示例代码展示 以下是一个简单的 ByteBuf 使用案例: ```java import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; public class SimpleByteBufUsage { public static void main(String[] args) { // 创建一个非池化的 ByteBuf ByteBuf buffer = Unpooled.buffer(10); // 向 Buffer 写入字节数据 String message = "Hello"; byte[] bytes = message.getBytes(); buffer.writeBytes(bytes); // 输出当前状态信息 System.out.println("Reader Index: " + buffer.readerIndex()); System.out.println("Writer Index: " + buffer.writerIndex()); // 移动 Reader Index 并读取第一个字符 char firstChar = (char) buffer.readByte(); System.out.println("First Character: " + firstChar); // 清理资源 buffer.release(); } } ``` 此程序演示了如何向 ByteBuf 写入字符串并从中提取单个字符的过程。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值