从NIO到netty(14) ByteBuf和ReferenceCounted

56 篇文章 2 订阅

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;
}
ActionWho should release?Who released?
1. main() creates bufbufmain() 
2. main() calls a() with bufbufa() 
3. a() returns buf merely.bufmain() 
4. main() calls b() with bufbufb() 
5. b() returns the copy of bufbufb()copymain()b() releases buf
6. main() calls c() with copycopyc() 
7. c() swallows copycopyc()c() releases copy

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 with ADVANCED 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)

贴上一篇文章 https://netty.io/wiki/reference-counted-objects.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1讲:学习的要义 第2讲:Netty宏观理解 第3讲:Netty课程大纲深度解读 第4讲:项目环境搭建与Gradle配置 第5讲:Netty执行流程分析与重要组件介绍 第6讲:Netty回调与Channel执行流程分析 第7讲:Netty的Socket编程详解 第8讲:Netty多客户端连接与通信 第9讲:Netty读写检测机制与长连接要素 第10讲:Netty对WebSocket的支援 第11讲:Netty实现服务器端与客户端的长连接通信 第12讲:Google Protobuf详解 第13讲:定义Protobuf文件及消息详解 第14讲:Protobuf完整实例详解 第15讲:Protobuf集成Netty与多协议消息传递 第16讲:Protobuf多协议消息支援与工程最佳实践 第17讲:Protobuf使用最佳实践与Apache Thrift介绍 第18讲:Apache Thrift应用详解与实例剖析 第19讲:Apache Thrift原理与架构解析 第20讲:通过Apache Thrift实现Java与Python的RPC调用 第21讲:gRPC深入详解 第22讲:gRPC实践 第23讲:Gradle Wrapper在Gradle项目构建中的最佳实践 第24讲:gRPC整合Gradle与代码生成 第25讲:gRPC通信示例与JVM回调钩子 第26讲:gRPC服务器流式调用实现 第27讲:gRPC双向流式数据通信详解 第28讲:gRPC与Gradle流畅整合及问题解决的完整过程与思考 第29讲:Gradle插件问题解决方案与Nodejs环境搭建 第30讲:通过gRPC实现Java与Nodejs异构平台的RPC调用 第31讲:gRPC在Nodejs领域中的静态代码生成及与Java之间的RPC调用 第32讲:IO体系架构系统回顾与装饰模式的具体应用 第33讲:Java NIO深入详解与体系分析 第34讲:Buffer中各重要状态属性的含义与关系图解 第35讲:Java NIO核心类源码解读与分析 第36讲:文件通道用法详解 第37讲:Buffer深入详解 第38讲:NIO堆外内存与零拷贝深入讲解 第39讲:NIO中Scattering与Gathering深度解析 第40讲:Selector源码深入分析 第41讲:NIO网络访问模式分析 第42讲:NIO网络编程实例剖析 第43讲:NIO网络编程深度解析 第44讲:NIO网络客户端编写详解 第45讲:深入探索Java字符集编解码 第46讲:字符集编解码全方位解析 第47讲:Netty服务器与客户端编码模式回顾及源码分析准备 第48讲:NettyNIO系统总结及NIONetty之间的关联关系分析 第49讲:零拷贝深入剖析及用户空间与内核空间切换方式 第50讲:零拷贝实例深度剖析 第51讲:NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 第52讲:NioEventLoopGroup源码分析与线程数设定 第53讲:Netty对Executor的实现机制源码分析 第54讲:Netty服务端初始化过程与反射在其中的应用分析 第55讲:Netty提供的Future与ChannelFuture优势分析与源码讲解 第56讲:Netty服务器地址绑定底层源码分析 第57讲:Reactor模式透彻理解及其在Netty中的应用 第58讲:Reactor模式与Netty之间的关系详解 第59讲:Acceptor与Dispatcher角色分析 第60讲:Netty的自适应缓冲区分配策略与堆外内存创建方式 第61讲:Reactor模式5大角色彻底分析 第62讲:Reactor模式组件调用关系全景分析 第63讲:Reactor模式与Netty组件对比及Acceptor组件的作用分析 第64讲:Channel与ChannelPipeline关联关系及模式运用 第65讲:ChannelPipeline创建时机与高级拦截过滤器模式的运用 第66讲:Netty常量池实现及ChannelOption与Attribute作用分析 第67讲:Channel与ChannelHandler及ChannelHandlerContext之间的关系分析 第68讲:Netty核心四大组件关系与构建方式深度解读 第69讲:Netty初始化流程总结及Channel与ChannelHandlerContext作用域分析 第70讲:Channel注册流程深度解读 第71讲:Channel选择器工厂与轮询算法及注册底层实现 第72讲:Netty线程模型深度解读与架构设计原则 第73讲:Netty底层架构系统总结与应用实践 第74讲:Netty对于异步读写操作的架构思想与观察者模式的重要应用 第75讲:适配器模式与模板方法模式在入站处理器中的应用 第76讲:Netty项目开发过程中常见且重要事项分析 第77讲:Java NIO Buffer总结回顾与难点拓展 第78讲:Netty数据容器ByteBuf底层数据结构深度剖析 第79讲:NettyByteBuf底层实现大揭秘 第80讲:Netty复合缓冲区详解与3种缓冲区适用场景分析 第81讲:Netty引用计数的实现机制与自旋锁的使用技巧 第82讲:Netty引用计数原子更新揭秘与AtomicIntegerFieldUpdater深度剖析 第83讲:AtomicIntegerFieldUpdater实例演练与volatile关键字分析 第84讲:Netty引用计数注意事项与内存泄露检测方式 第85讲:Netty编解码器剖析与入站出站处理器详解 第86讲:Netty自定义编解码器与TCP粘包拆包问题 第87讲:Netty编解码器执行流程深入分析 第88讲:ReplayingDecoder源码分析与特性解读 第89讲:Netty常见且重要编解码器详解 第90讲:TCP粘包与拆包实例演示及分析 第91讲:Netty自定义协议与TCP粘包拆包问题解决之道 第92讲:精通并发与Netty课程总结与展望

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值