ByteBuf是Netty提供的代替jdk的ByteBuffer的一个容器,首先看一下他的具体用法:
public class ByteBufTest0 {
public static void main(String[] args) {
ByteBuf byteBuf = Unpooled.buffer(10);//堆缓冲区
for(int i=0;i<byteBuf.capacity();i++){
byteBuf.writeByte(i);
}
//绝对方式 不会改变readerIndex
for(int i=0;i<byteBuf.capacity();i++){
System.out.println(byteBuf.getByte(i));
}
//相对方式 会改变writerIndex
for(int i=0;i<byteBuf.capacity();i++){
System.out.println(byteBuf.readByte());
}
}
}
+-------------------+------------------+------------------+
* | discardable bytes | readable bytes | writable bytes |
* +-------------------+------------------+------------------+
* | | | |
* 0 <= readerIndex <= writerIndex <= capacity
readerIndex 控制读的游标,writerIndex 控制写的游标,capacity是容量。
ByteBuf根据堆外内存还是堆内内存可以分成三种类型
Heap Buffer
* 这是最常用的类型,ByteBuf将数据存储在JVM堆空间中,并且将实际的数据存放在byte array中来实现。
* 优点:由于数据是存储在JVM堆中,所以可以实现快速的创建和快速释放,并且提供了直接访问内部字节数组的方法。
* 缺点:每次读写数据必须将数组复制到直接缓冲区,再进行网络传输
*
* Direct Buffer
* 在堆外直接分配内存空间,直接缓冲区并不会占用堆的容量空间,因为是操作系统直接在本地内存分配数据。
* 优点:在socket进行数据传递时,性能非常好,因为数据直接位于操作系统本地内存中,所以不需要从JVM将数据复制到直接缓冲区,性能很好。
* 缺点:因为DirectBuffer在操作系统内存中,所以分配内存空间和释放比堆要负复杂。所以速度要慢一点
* Netty通过提供内存池来解决这个问题。内存池是提前申请好的内存空间。直接缓冲区并不支持通过字节数组的方式来访问数据。
*
* 重点:对于后端的业务消息编解码来说,推荐使用HeapByteBuf;对于I/O通信线程在读写缓冲区时,推荐使用DirectByteBuf
*
* 1.Netty的ByteBuf采用了读写索引分离策略,一个初始化的ByteBuf的readerIndex和writerIndex都为0
* 当读索引和写索引出于同一个位置时,如果我们继续读取,就会抛异常
* 2.对于ByteBuf的任何读写都会分别维护读索引和写索引,如果容量不够会自动陪你过扩,JDK会报错。maxCapticy最大容量默认的限制就是Integer.MAX_VALUE;
如果按照池化和非池化可以分成两类型
JDK的ByteBuffer的缺点:
1、final byte[] bb 这是JDK的ByteBuffer对象中用于存储数据的对象声明,可以看到,其字节数组是被声明为final的,也就是长度是固定不变的,一旦分配好后不能动态扩容与收缩,
而且当待存储的数据字节很大时就很有可能出现那么就会抛出IndexOutOfBoundException,如果要预防这个异常,那就需要在存储事前完全确定好待存储的字节大小。如果ByteBuffer的空间不足,我们只有一种解决方案:
创建一个全新的ByteBuffer对象,然后再将之前的ByteBuffer中的数据复制过去,这一切都需要开发者自己来手动完成。
2、ByteBuffer只是用一个position指针来标示位置信息,在进行读写切换时就需要调用flip方法或是rewind方法,使用起来很不方便。
Netty的ByteBuf的优点:
1、存储字节的数组是动态的,其最大值默认是Integer.MAX_VALUE,这里的动态性是体现在write方法中的,write方法在执行时会判断buffer容量,如果不足则自动扩容。
2、ByteBuf的读写索引是完全分开的,使用起来很方便。
------------------------------------------
下面说一下引用计数:
我们知道JVM可以回收失去引用的对象,这里采用的是可达性分析法。但是堆外内存是不受JVM控制的,要回收垃圾,就不能依赖JVM的GC,所以这里就引入了ReferenceCounted,采用引用计数法来进行垃圾回收
当引用计数为0的时候,bytebuf数据会被放回池空间或者回收,这取决于用的是pooled还是unpooled
public interface ReferenceCounted {
这两个方法是增加引用计数的,上面是+1,下面可以指定
ReferenceCounted retain();
ReferenceCounted retain(int increment);
这两个方法是用于监测用的
ReferenceCounted touch();
ReferenceCounted touch(Object hint);
这两个方法是减少引用计数的,上面是-1,下面可以指定
boolean release();
boolean release(int decrement);
}
AbstractReferenceCounted#retain0
private ReferenceCounted retain0(int increment) {
for (;;) {
int refCnt = this.refCnt;
final int nextCnt = refCnt + increment;
// Ensure we not resurrect (which means the refCnt was 0) and also that we encountered an overflow.
if (nextCnt <= increment) {
throw new IllegalReferenceCountException(refCnt, increment);
}
if (refCntUpdater.compareAndSet(this, refCnt, nextCnt)) {
break;
}
}
return this;
}
可以发现retain0是采用自旋加cas的操作来更新
这里有一个
private static final AtomicIntegerFieldUpdater<AbstractReferenceCounted> refCntUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCounted.class, "refCnt");
这里主要说明下AtomicIntegerFieldUpdater的使用
AtomicIntegerFieldUpdater使用反射 更新某个类的内部的一个int类型的并且是volatitle的变量。
这里提一下AtomicIntegerFieldUpdater:
1、更新器更新的必须int类型的变量 ,不能是其包装类型。
2、更新器更新的必须是volatitle类型变量,确保线程之间共享变量时的立即可见性。
AtomicIntegerFieldUpdater.newUpdater()方法的实现是AtomicIntegerFieldUpdaterImpl:
AtomicIntegerFieldUpdaterImpl的构造器会对类型进行验证:
if (field.getType() != int.class)
throw new IllegalArgumentException("Must be integer type");
if (!Modifier.isVolatile(modifiers))
throw new IllegalArgumentException("Must be volatile type");
3、变量不能是static的,必须是实例变量,因为Unsafe.objectFieldOffset()方法不支持静态变量(cas操作本质上是通过对象实例的偏移量来直接进行赋值)
4、更新器只能修改可见范围内的变量,因为更新器是通过反射来得到这个变量,如果变量不可见就会报错。
这里解释下为什么引用计数没有通过for(;;){
AtmoicInteger
}
来实现?
因为如果用AtmoicInteger来实现,那么每一个bytebuf都要有AtmoicInteger来维护这个引用计数,而
AtomicIntegerFieldUpdater是一个类变量,只有一份。这样变相提高了性能。
---------
如果一个byteBuf的引用计数为0,再次调用写方法就会抛异常:
assert buf.refCnt() == 0;
try {
buf.writeLong(0xdeadbeef);
throw new Error("should not reach here");
} catch (IllegalReferenceCountExeception e) {
// Expected
}
关于谁去调用release方法:
通用的规则就是谁在最后接触这个byteBuf就去调用realse方法。详细的:
1.如果一个sending组件把一个bytebuf传递给了一个receiving组件,那么sending组件不需要去调用release方法,receiving组件
需要负责调用release方法
2.如果一个消费组件消费了bytebuf,并且知道没有组件再去引用它,那么久需要调用release方法
下面有个例子
public ByteBuf a(ByteBuf input) {
input.writeByte(42);
return input;
}
public ByteBuf b(ByteBuf input) {
try {
output = input.alloc().directBuffer(input.readableBytes() + 1);
output.writeBytes(input);
output.writeByte(42);
return output;
} finally {
input.release();
}
}
public void c(ByteBuf input) {
System.out.println(input);
input.release();
}
public void main() {
...
ByteBuf buf = ...;
// This will print buf to System.out and destroy it.
c(b(a(buf)));
assert buf.refCnt() == 0;
}
Action | Who should release? | Who released? |
1. |
| |
2. |
| |
3. |
| |
4. |
| |
5. |
|
|
6. |
| |
7. |
|
|
ByteBuf.duplicate()
, ByteBuf.slice()
and ByteBuf.order(ByteOrder)
ByteBufHolder
这些方法调用产生的ByteBuf和调用的byteBuf共享一个引用计数,而 ByteBuf.copy()
and ByteBuf.readBytes(int)产生的ByteBuf和原来的ByteBuf不共用一个引用计数。
因此:
ByteBuf parent = ctx.alloc().directBuffer(512);
parent.writeBytes(...);
try {
while (parent.isReadable(16)) {
ByteBuf derived = parent.readSlice(16);
derived.retain();-------1
process(derived);
}
} finally {
parent.release();
}
...
public void process(ByteBuf buf) {
...
buf.release();
}
上面这段代码1这行代码千万不能删,如果删除了就会报错
当事件循环将数据读取到bytebuf中并用它触发channelread()事件时,相应管道中的channelhandler负责释放缓冲区。因此,使用接收数据的处理程序应该对其channelRead()处理程序方法中的数据调用release():
public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf buf = (ByteBuf) msg; try { ... } finally { buf.release(); } }
当然如果一个bytebuf被传递到下一个handler,那么将不负责调用release方法:
public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf buf = (ByteBuf) msg; ... ctx.fireChannelRead(buf); }
如果你自己都搞不清释放那个bytebuf了,可以直接调用
ReferenceCountUtil.release(msg);
SimpleChannelHandler中就已经调用了ReferenceCountUtil.release(msg);
与入站处理器不同的时候,出站处理器的消息是由netty创建的,所以当消息被写入channel之后由netty来负责释放消息。但是,拦截写请求的处理程序应该确保正确地释放任何中间对象。(例如编码器)
为了帮助你诊断潜在的(资源泄漏)问题,Netty提供了class ResourceLeakDetector1, 它将对你应用程序的缓冲区分配做大约 1%的采样来检测内存泄露。相关的开销是非常小的。
如果检测到了内存泄露,将会产生类似于下面的日志消息:
LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option '-Dio.netty.leakDetectionLevel=ADVANCED' or call ResourceLeakDetector.setLevel().
Netty 目前定义了 4 种泄漏检测级别,
-
DISABLED
- disables leak detection completely. Not recommended. -
SIMPLE
- tells if there is a leak or not for 1% of buffers. Default. -
ADVANCED
- tells where the leaked buffer was accessed for 1% of buffers. -
PARANOID
- Same withADVANCED
except that it's for every single buffer. Useful for automated testing phase. You could fail the build if the build output contains 'LEAK:
'.
泄露检测级别可以通过将下面的 Java 系统属性设置为表中的一个值来定义:
java -Dio.netty.leakDetectionLevel=ADVANCED
如果带着该 JVM 选项重新启动你的应用程序,你将看到自己的应用程序最近被泄漏的缓冲 区被访问的位置。下面是一个典型的由单元测试产生的泄漏报告:
Running io.netty.handler.codec.xml.XmlFrameDecoderTest
15:03:36.886 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 1
#1:
io.netty.buffer.AdvancedLeakAwareByteBuf.toString(AdvancedLeakAwareByteBuf.java:697)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:157)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)
...
Created at:
io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)
io.netty.buffer.UnpooledUnsafeDirectByteBuf.copy(UnpooledUnsafeDirectByteBuf.java:465)
io.netty.buffer.WrappedByteBuf.copy(WrappedByteBuf.java:697)
io.netty.buffer.AdvancedLeakAwareByteBuf.copy(AdvancedLeakAwareByteBuf.java:656)
io.netty.handler.codec.xml.XmlFrameDecoder.extractFrame(XmlFrameDecoder.java:198)
io.netty.handler.codec.xml.XmlFrameDecoder.decode(XmlFrameDecoder.java:174)
io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:227)
io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:140)
io.netty.channel.ChannelHandlerInvokerUtil.invokeChannelReadNow(ChannelHandlerInvokerUtil.java:74)
io.netty.channel.embedded.EmbeddedEventLoop.invokeChannelRead(EmbeddedEventLoop.java:142)
io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:317)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:176)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:147)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)
...
如果是netty还将提供额外的信息,比如是哪一个handler发生内存泄露了
比如下面的例子:
12:05:24.374 [nioEventLoop-1-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 2
#2:
Hint: 'EchoServerHandler#0' will handle the message from this point.
io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:329)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:133)
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
java.lang.Thread.run(Thread.java:744)
#1:
io.netty.buffer.AdvancedLeakAwareByteBuf.writeBytes(AdvancedLeakAwareByteBuf.java:589)
io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:208)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125)
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
java.lang.Thread.run(Thread.java:744)
Created at:
io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:146)
io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:107)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:123)
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
java.lang.Thread.run(Thread.java:744)