现在需要做一个流媒体服务器,打算使用Netty来实现协议的解析,视频流的转码等。数据的传输载体使用的是ByteBuf,但是写完代码进行单元压测时,老是内存泄漏,现在记录一下。
Netty里四种主力的ByteBuf
其中UnpooledHeapByteBuf 底下的byte[]能够依赖JVM GC自然回收;而UnpooledDirectByteBuf底下是DirectByteBuffer,而PooledHeapByteBuf 和PooledDirectByteBuf,则必须要主动将用完的byte[]/ByteBuffer放回池里,否则内存就要爆掉。所以,Netty ByteBuf需要在JVM的GC机制之外,有自己的引用计数器和回收过程。
下面用代码记录下:
错误实例:在申请了DirectByteBuf后不进行释放,导致内存飙升,程序报错。
public class test {
public static void main(String[] args) {
PooledByteBufAllocator allocator = new PooledByteBufAllocator(false);
while (true){
ByteBuf byteBuf = allocator.directBuffer();
byteBuf.writeBytes("hello.. I am Netty.".getBytes());
}
}
}
报错信息:
17:28:17.584 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records:
Created at:
io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:349)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:187)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:173)
com.tsingtech.jtt1078.test.test.main(test.java:11)
正确的操作:(在使用后进行释放)
public class test {
public static void main(String[] args) {
PooledByteBufAllocator allocator = new PooledByteBufAllocator(false);
while (true){
ByteBuf byteBuf = allocator.directBuffer();
byteBuf.writeBytes("hello.. I am Netty.".getBytes());
ReferenceCountUtil.safeRelease(byteBuf);
}
}
}
根据 Wiki 对 Zero-copy 的定义:
"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. This is frequently used to save CPU cycles and memory bandwidth when transmitting a file over a network.
即所谓的 Zero-copy
, 就是在操作数据时, 不需要将数据 buffer 从一个内存区域拷贝到另一个内存区域. 因为少了一次内存的拷贝, 因此 CPU 的效率就得到的提升.
在 OS 层面上的 Zero-copy
通常指避免在 用户态(User-space)
与 内核态(Kernel-space)
之间来回拷贝数据. 例如 Linux 提供的 mmap
系统调用, 它可以将一段用户空间内存映射到内核空间, 当映射成功后, 用户对这段内存区域的修改可以直接反映到内核空间; 同样地, 内核空间对这段区域的修改也直接反映用户空间. 正因为有这样的映射关系, 我们就不需要在 用户态(User-space)
与 内核态(Kernel-space)
之间拷贝数据, 提高了数据传输的效率.
而需要注意的是, Netty 中的 Zero-copy
与上面我们所提到到 OS 层面上的 Zero-copy
不太一样, Netty的 Zero-coyp
完全是在用户态(Java 层面)的, 它的 Zero-copy
的更多的是偏向于 优化数据操作
这样的概念.
Netty 的 Zero-copy
体现在如下几个个方面:
CompositeByteBuf
类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf 之间的拷贝.FileRegion
包装的FileChannel.tranferTo
实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel
, 避免了传统通过循环 write 方式导致的内存拷贝问题.在Netty中经常使用的API是 slice(),compositeBuffer()
看下发代码示例:最后虽然释放的是byteBufs,相当于同时释放了byteBuf-byteBuf1-byteBuf2,还有注意到变量slice是通过byteBuf截取得到的,对slice释放也释放了byteBuf。这是因为slice()方法和原始的byteBuf共享计数器。
public class test {
public static void main(String[] args) {
PooledByteBufAllocator allocator = new PooledByteBufAllocator(false);
while (true){
ByteBuf byteBuf = allocator.directBuffer().writeBytes("hello.. I am Netty.".getBytes());
ByteBuf byteBuf1 = allocator.directBuffer().writeBytes("test1".getBytes());
ByteBuf byteBuf2 = allocator.directBuffer().writeBytes("test2".getBytes());
ByteBuf slice = byteBuf.slice(1, 5);
CompositeByteBuf byteBufs = allocator.compositeBuffer(3)
.addComponent(true, slice)
.addComponent(true, byteBuf1)
.addComponent(true, byteBuf2);
ReferenceCountUtil.safeRelease(byteBufs);
}
}
}