欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

【Netty】「萌新入门」(七)ByteBuf 的性能优化-堆内存的分配和释放都是由 Java 虚拟机自动管理的,这意味着它们可以快速地被分配和释放,但是也会产生一些开销。 直接内存需要手动分配和释放,因为它由操作系统管理,这使得分配和释放的速度更快,但是也需要更多的系统资源。 另外,直接内存可以映射到本地文件中,这对于需要频繁读写文件的应用程序非常有用。 此外,直接内存还可以避免在使用 NIO 进行网络传输时发生数据拷贝的情况。在使用传统的 I/O 时,数据必须先从文件或网络中读取到堆内存中,然后再从堆内存中复制到直接缓冲区中,最后再通过 SocketChannel 发送到网络中。而使用直接缓冲区时,数据可以直接从文件或网络中读取到直接缓冲区中,并且可以直接从直接缓冲区中发送到网络中,避免了不必要的数据拷贝和内存分配。 通过 ByteBufAllocator.DEFAULT.directBuffer 方法来创建基于直接内存的 ByteBuf: ByteBuf directBuf = ByteB

最编程 2024-01-14 14:21:26
...

使用 release() 方法将 ByteBuf 的计数减为0时,再次调用 ByteBuf,测试代码:

public static void testRelease() {  
    ByteBuf buf = ByteBufAllocator.DEFAULT.heapBuffer(16);  
    System.out.println("ByteBuf 的引用计数为 " + buf.refCnt());  
    buf.writeBytes("sidiot.".getBytes());  
    log(buf);  
    buf.release();  
    log(buf);  
}

运行结果:

ByteBuf 的引用计数为 1

read index:0 write index:7 capacity:16
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 2e                            |sidiot.         |
+--------+-------------------------------------------------+----------------+

Exception in thread "main" io.netty.util.IllegalReferenceCountException: refCnt: 0
	at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1464)
	at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1388)
	at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1384)
	at io.netty.buffer.AbstractByteBuf.getByte(AbstractByteBuf.java:361)
	at io.netty.buffer.AbstractByteBuf.getUnsignedByte(AbstractByteBuf.java:374)
	at io.netty.buffer.ByteBufUtil$HexUtil.appendPrettyHexDump(ByteBufUtil.java:1143)
	at io.netty.buffer.ByteBufUtil$HexUtil.access$300(ByteBufUtil.java:982)
	at io.netty.buffer.ByteBufUtil.appendPrettyHexDump(ByteBufUtil.java:978)
	at io.netty.buffer.ByteBufUtil.appendPrettyHexDump(ByteBufUtil.java:969)
	at com.sidiot.netty.c4.TestByteBuf.log(TestByteBuf.java:93)
	at com.sidiot.netty.c4.TestByteBuf1.testRefCount(TestByteBuf1.java:49)
	at com.sidiot.netty.c4.TestByteBuf1.main(TestByteBuf1.java:10)

使用 retain() 方法将 ByteBuf 的计数加为3时,再次调用 ByteBuf,测试代码:

public static void testRetain() {  
    ByteBuf buf = ByteBufAllocator.DEFAULT.heapBuffer(16);  
    System.out.println("ByteBuf 的引用计数为 " + buf.refCnt());  
    buf.writeBytes("sidiot.".getBytes());  
    log(buf);  
    buf.retain();  
    buf.retain();  
    System.out.println("ByteBuf 的引用计数为 " + buf.refCnt());  
    log(buf);  
}

运行结果:

ByteBuf 的引用计数为 1

read index:0 write index:7 capacity:16
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 2e                            |sidiot.         |
+--------+-------------------------------------------------+----------------+

ByteBuf 的引用计数为 3

read index:0 write index:7 capacity:16
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 2e                            |sidiot.         |
+--------+-------------------------------------------------+----------------+

在博文 Pipeline 与 ChannelHandler 中,ByeBuf 实例通过 Pipeline 传递给下一个 ChannelHandler,这时,我们要确保 ByeBuf 实例的计数大于0,否则将不能被下一个 ChannelHandler 所使用,除非 ByeBuf 实例完成了它的使命,不需要再往下传递,此时,通过 release() 进行内存释放;

因此,ByeBuf 有一个基本的内存释放规则,谁最后使用,谁来释放内存

  • 将 ByteBuf 通过 ctx.fireChannelRead(msg) 方法传递给下一个 ChannelHandler,无需 release()
  • 将 ByteBuf 转换为其他类型对象,ByteBuf 使命结束,必须 release()
  • 不将 ByteBuf 继续向后传递,必须 release()
  • 捕获各种异常,如果 ByteBuf 在传递过程中抛出异常,必须 release()
  • 消息传至 Head 或者 Tail 后,消息会被其中的方法彻底释放;
public static boolean release(Object msg) { 
    return msg instanceof ReferenceCounted ? ((ReferenceCounted)msg).release() : false; 
}

逻辑切片与合并

ByteBuf 切片 Slice 与 CompositeByteBuf 合并 addComponents 都是是零拷贝的表现;

ByteBuf 切片本质上就是对原始 ByteBuf 进行分段,每一段都是一个独立的 ByteBuf 对象,但是它们共享同一块内存空间,并且维护着各自的 readwrite 指针。

由于通过切片得到的 ByteBuf 对象共享同一块内存空间,因此在进行数据读写操作时不需要进行数据拷贝或者内存复制,这样可以避免不必要的性能开销和内存占用。

值得注意的是,当我们得到分片后的 ByteBuf 对象时,需要手动调用其 retain() 方法使其内部的引用计数加一,以保证原始 ByteBuf 在使用过程中不被释放导致切片后的 ByteBuf 无法使用。在使用完毕之后,还需要手动调用 release() 方法来释放资源,以防止内存泄漏的发生。

Netty-切片slice.png

CompositeByteBuf 可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf。而 addComponents 方法就是用来向 CompositeByteBuf 中添加多个 ByteBuf 的。

具体来说,addComponents 方法接受一个参数 components,这个参数是一个 ByteBuf 数组,表示要添加的 ByteBuf 集合。调用 addComponents 方法后,会将传入的 ByteBuf 数组中的所有 ByteBuf 都添加到 CompositeByteBuf 中,然后返回值是一个 CompositeByteBuf 对象,它包含了所有添加的 ByteBuf。

需要注意的是,添加的 ByteBuf 必须已经被分配和写入了数据,否则在访问 CompositeByteBuf 时可能会出现异常。此外,在使用完 CompositeByteBuf 后,需要及时释放它所持有的所有 ByteBuf,以避免内存泄漏。

Netty-合并Composite.png


测试代码:

public static void testZeroCopy() {  
    ByteBuf buf = ByteBufAllocator.DEFAULT.heapBuffer(16);  
    buf.writeBytes("sidiot.".getBytes());  
    log(buf);  

    System.out.println("\n<===== 测试 slice 方法 ======>\n");  
    ByteBuf s1 = buf.slice(0, 1);  
    ByteBuf s2 = buf.slice(1, 5);  
    s1.retain();  
    System.out.println("=====> s1 <======");  
    log(s1);  
    s2.retain();  
    System.out.println("\n=====> s2 <======");  
    log(s2);  

    System.out.println("\n<===== 修改原始 ByteBuf 中的值 ======>\n");  
    buf.setBytes(3, new byte[]{'1' , '0'});  
    System.out.println("=====> buf <======");  
    log(buf);  
    System.out.println("\n=====> s2 <======");  
    log(s2);  

    System.out.println("\n<===== 测试 CompositeByteBuf 类 ======>\n");  
    CompositeByteBuf cBuf = ByteBufAllocator.DEFAULT.compositeBuffer();  
    cBuf.addComponents(true, s1, s2, s1);  
    System.out.println("=====> cBuf <======");  
    log(cBuf);  
}

运行结果:

read index:0 write index:7 capacity:16
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 2e                            |sidiot.         |
+--------+-------------------------------------------------+----------------+

<===== 测试 slice 方法 ======>

=====> s1 <======
read index:0 write index:1 capacity:1
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 73                                              |s               |
+--------+-------------------------------------------------+----------------+

=====> s2 <======
read index:0 write index:5 capacity:5
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 69 64 69 6f 74                                  |idiot           |
+--------+-------------------------------------------------+----------------+

<===== 修改原始 ByteBuf 中的值 ======>

=====> buf <======
read index:0 write index:7 capacity:16
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 31 30 74 2e                            |sid10t.         |
+--------+-------------------------------------------------+----------------+

=====> s2 <======
read index:0 write index:5 capacity:5
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 69 64 31 30 74                                  |id10t           |
+--------+-------------------------------------------------+----------------+

<===== 测试 CompositeByteBuf 类 ======>

=====> cBuf <======
read index:0 write index:1 capacity:1
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 73                                              |s               |
+--------+-------------------------------------------------+----------------+

需要注意的是,addComponents() 方法是不会主动去调整写入 buf 的指针位置的,因此如果第一个参数不填写 true 的话,结果会是如下所示:

=====> cBuf <======
read index:0 write index:0 capacity:7

另外还需要注意的地方就是,如果我们释放 cBuf,那么他会把逻辑上合并的 buf 全部释放一遍,我们结合代码进行理解,在刚刚的测试代码末尾加上如下代码:

System.out.println("buf 的引用计数为 " + buf.refCnt());  
System.out.println("cBuf 的引用计数为 " + cBuf.refCnt());  
cBuf.release();  
System.out.println("cBuf 的引用计数为 " + cBuf.refCnt());  
System.out.println("buf 的引用计数为 " + buf.refCnt());

运行结果:

buf 的引用计数为 3
cBuf 的引用计数为 1
cBuf 的引用计数为 0
buf 的引用计数为 0

可以发现,不止 cBuf 计数为0,buf 的计数也为0;

如果还无法理解,我们继续测试,将 CompositeByteBuf 只用两个 buf 进行合并,即 cBuf.addComponents(true, s1, s2);,运行结果如下:

buf 的引用计数为 3
cBuf 的引用计数为 1
cBuf 的引用计数为 0
buf 的引用计数为 1

正如我们所预测的那样,buf 的计数为1,因此,在释放 cBuf 时需谨慎!

后记

通过对 ByteBuf 的性能优化,我们可以显著提升应用程序的效率和吞吐量。首先,选择正确的内存模式对于性能至关重要,使用直接内存模式可以减少不必要的数据复制,提高读写操作的速度。其次,池化技术可以有效地重用 ByteBuf 实例,减少内存分配的开销。这样可以避免频繁的垃圾回收,提高整体的性能表现。

此外,合理释放内存也是性能优化的一个关键点。通过手动释放不再使用的 ByteBuf 实例,可以及时回收内存资源,防止内存泄漏的发生。最后,逻辑切片与合并操作可以有效地减少数据复制的次数,降低 CPU 的负载,提高系统的响应速度。

以上就是 ByteBuf 的性能优化 的所有内容了,希望本篇博文对大家有所帮助!

参考:

  • Netty API reference
  • 黑马程序员Netty全套教程

???? 上篇精讲:「萌新入门」(六)ByteBuf 的基本使用

???? 我是 ????????????????????????,期待你的关注,创作不易,请多多支持;

???? 公众号:sidiot的技术驿站

???? 系列专栏:探索 Netty:源码解析与应用案例分享

推荐阅读