zoukankan      html  css  js  c++  java
  • Netty ByteBuf和Nio ByteBuffer

    参考https://blog.csdn.net/jeffleo/article/details/69230112

    一、简介

    Netty中引入了ByteBuf,它相对于ByteBuffer来说,带来了很多便捷性和创新的地方,使得程序员更简单得进行网络编程

    二、ByteBuffer的缺点和ByteBuf的改进

    下面我们从几个点来分别讲解:

    1. 扩容

    ByteBuffer:

    ByteBuffer缓冲区的长度固定,分多了会浪费内存,分少了存放大的数据时会索引越界,所以使用ByteBuffer时,为了解决这个问题,我们一般每次put操作时,都会对可用空间进行校检,如果剩余空间不足,需要重新创建一个新的ByteBuffer,然后将旧的ByteBuffer复制到新的ByteBuffer中去。

    ByteBuf:

    而ByteBuf则对其进行了改进,它会自动扩展,具体的做法是,写入数据时,会调用ensureWritable方法,传入我们需要写的字节长度,判断是否需要扩容:

    public ByteBuf ensureWritable(int minWritableBytes) {
    if(minWritableBytes < 0) {
    throw Exception
    } else if(minWritableBytes <= this.writableBytes()) {
    return this;
    } else if(minWritableBytes > this.maxCapacity - this.writerIndex) {
    throw Exception
    } else {
    //扩容
    int newCapacity = this.calculateNewCapacity(this.writerIndex + minWritableBytes);
    this.capacity(newCapacity);
    return this;
    }
    }

    private int calculateNewCapacity(int minNewCapacity) {
    int maxCapacity = this.maxCapacity;
    int threshold = 4194304; //4mb阀值
    if(minNewCapacity == 4194304) {//如果新容量为阀值,直接返回
    return 4194304;
    } else {
    int newCapacity;
    if(minNewCapacity > 4194304) {//如果传入的新容量大于阀值,进行计算
    newCapacity = minNewCapacity / 4194304 * 4194304;
    if(newCapacity > maxCapacity - 4194304) {//如果大于最大容量,新容量为最大容量
    newCapacity = maxCapacity;
    } else {//否则新容量 + 阀值 4mb,按照阀值扩容
    newCapacity += 4194304;
    }

    return newCapacity;
    } else {//如果小于阀值,则以64为计数倍增,知道倍增的结果>=需要的容量值
    for(newCapacity = 64; newCapacity < minNewCapacity; newCapacity <<= 1) {
    ;
    }

    return Math.min(newCapacity, maxCapacity);
    }
    }
    }

    这里要解释的地方:
    1. 当申请的新空间大于阀值时,采用每次步进4MB的方式进行扩张内存,而不是倍增,因为这会造成内存膨胀和浪费
    2. 而但申请的新空间小于阀值时,则以64为基数进行倍增而不是步进,因为当内存比较小的时候,倍增是可以接受的(64 -> 128 和 10Mb -> 20Mb相比)

    2. 位置指针

    ByteBuffer:

    ByteBuffer中只有一个位置指针position(ByteBuf有两个),所以需要我们手动得调用flip等方法,例如:

    ByteBuffer buffer = ByteBuffer.allocate(88);
    String value = "我的博客";
    buffer.put(value.getBytes());
    buffer.flip();
    byte[] array = new byte[buffer.remaining()];
    buffer.get(array);
    String decodeValue = new String(array);
    ByteBuffer中会有三个下标,初始位置0,当前位置positon,limit位置,初始时,position为0,limit为Buffer数组末尾 
    调用buffer.put(value.getBytes())后: 
    这里写图片描述

    不调用flip:
    从缓冲区读取的是position — limit位置的数据,明显不是我们要的
    调用flip:
    会将limit设置为position,position设置为0,,此时读取的数据


    ByteBuf:

    ByteBuf中使用两个指针,readerIndex,writerIndex来指示位置,初始时readrIndex = writerIndex = 0,当写入数据后:

    writerIndex — capacity:可写容量
    readerIndex — writerIndex:可读部分

    当读取了M个字节后:


    调用discardReadBytes,会释放掉discardReadBytes的空间,并把readableBytes复制到从0开始的位置,因此这里会发生内存复制,频繁调用会影响性能


    三、ByteBuf分析

    ByteBuf最重要的两个基类,AbstractByteBuf提供了骨干实现,AbstractReferenceCountedByteBuf是AbstractByteBuf的子类,它的子类很常使用


    1. 分类

    1.1 从内存分配角度分类:

    (1)堆内存字节缓冲区(HeapByteBuf):UnPoolHeapByteBuf、PooledHeapByteBuf
    它的特点是内存的分配和回收都在堆,所以速度很快;缺点就是进行Socket的IO读写,需要把堆内存对应的缓冲区复制到内核Channel中,这内存复制会影响性能
    (2)直接内存缓冲区(DirectByteBuf):UnPoolDirectByteBuf、UnPoolUnsafeDirectByteBuf、PoolDirectByteBuf、PoolUnsafeDirectByteBuf
    它的特点是由于内存的分配在非堆(方法区),不需要内存复制,所以IO读取的速度较快,但是内存的分配较慢

    总结:
    根据两种内存的特点,我们可以知道,IO读写时最好使用DirectByteBuf,而在后端业务消息的解编码最好使用HeapByteBuf

    1.2 从内存回收的角度分类

    (1)基于对象池的ByteBuf(PoolByteBuf):PooledByteBuf和它的子类PoolDirectByteBuf、PoolUnsafeDirectByteBuf、PooledHeapByteBuf
    它的特点是可以循环利用创建的ByteBuf,提高了内存的使用效率,PoolByteBuf的实现牵涉的数据结构很多,PoolByteBuf首先会申请一大块内存区域PoolArena,PoolArena由多个Chunk组成,而每个Chunk由一个或多个page组成
    具体看《Netty权威指南》;
    也可以看:
    深入浅出Netty内存管理 PoolChunk
    深入浅出Netty内存管理 PoolSubpage
    深入浅出Netty内存管理 PoolChunkList
    深入浅出Netty内存管理 PoolArena

    (2)普通的ByteBuf(UnPoolByteBuf):UnPoolDirectByteBuf、UnPoolUnsafeDirectByteBuf、UnPoolHeapByteBuf

    总结:
    在高负载,大并发的情况下对象池的ByteBuf更好,而在一般情况下,可以使用UnPoolByteBuf

    2. Netty的零拷贝

    Netty的零拷贝主要体现在三个方面:

    第一种实现:DirectByteBuf

    就如上所说,ByteBuf可以分为HeapByteBuf和DirectByteBuf,当使用DirectByteBuf可以实现零拷贝

    第二种实现:CompositeByteBuf

    CompositeByteBuf将多个ByteBuf封装成一个ByteBuf,对外提供封装后的ByteBuf接口

    第三种实现:DefaultFileRegion

    DefaultFileRegion是Netty的文件传输类,它通过transferTo方法将文件直接发送到目标Channel,而不需要循环拷贝的方式,提升了传输性能

    3. Netty的内存回收管理

    Netty会通过 引用计数法 及时申请释放不再被引用的对象 ,实现上是通过
    AbstractReferenceCountedByteBuf来实现的,我们看上面的结构图,可以看到AbstractReferenceCountedByteBuf是AbstractByteBuf的直接子类,所有具体的实现ByteBuf(堆,非堆等)都是继承自AbstractReferenceCountedByteBuf,也就是说,Netty的具体的实现ByteBuf,都是具有内存回收管理的功能的

    AbstractReferenceCountedByteBuf有两个重要的成员变量:
    1、AtomicIntegerFieldUpdater< AbstractReferenceCountedByteBuf> refCntUpdater
    用来更新引用数,使用原子类,达到线程安全

    2、volatile int refCnt = 1
    用来记录引用数,保证可见性

    引用+1

    public ByteBuf retain() {
    int refCnt;
    do {
    refCnt = this.refCnt;
    if(refCnt == 0) {
    throw new IllegalReferenceCountException(0, 1);
    }

    if(refCnt == 2147483647) {
    throw new IllegalReferenceCountException(2147483647, 1);
    }
    } while(!refCntUpdater.compareAndSet(this, refCnt, refCnt + 1));

    return this;
    }

    public ByteBuf retain() {
    int refCnt;
    do {
    refCnt = this.refCnt;
    if(refCnt == 0) {
    throw new IllegalReferenceCountException(0, 1);
    }

    if(refCnt == 2147483647) {
    throw new IllegalReferenceCountException(2147483647, 1);
    }
    } while(!refCntUpdater.compareAndSet(this, refCnt, refCnt + 1));

    return this;
    }

    引用-1

    public final boolean release() {
    int refCnt;
    do {
    refCnt = this.refCnt;
    if(refCnt == 0) {
    throw new IllegalReferenceCountException(0, -1);
    }
    } while(!refCntUpdater.compareAndSet(this, refCnt, refCnt - 1));

    if(refCnt == 1) {
    this.deallocate();
    return true;
    } else {
    return false;
    }
    }

    public final boolean release() {
    int refCnt;
    do {
    refCnt = this.refCnt;
    if(refCnt == 0) {
    throw new IllegalReferenceCountException(0, -1);
    }
    } while(!refCntUpdater.compareAndSet(this, refCnt, refCnt - 1));

    if(refCnt == 1) {
    this.deallocate();
    return true;
    } else {
    return false;
    }
    }


    可以看出,无论是增加引用还是释放引用,都是使用了CAS

    refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)
    1
    对象时this,自身,也就是说,统计的是自身的引用数,例如对于UnPoolHeapByteBuf来说,它具有统计有多少对象引用着它,当引用数refCnt == 1时,表示此事已经没有对象引用它了,此时便调用deallocate来释放内存
    ---------------------
    作者:JeffCoding
    来源:CSDN
    原文:https://blog.csdn.net/jeffleo/article/details/69230112
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    FileZilla相关配置说明
    pod install速度慢,pod repo update 速度慢解决方法
    Vue安装
    mac 远程桌面连接
    .NET MVC 支付宝支付(即时到账)
    windows下生成ssl
    gentelella 开源后台使用记录
    node、npm、gulp安装
    武汉在院新冠肺炎患者清零
    致最可爱的战疫人
  • 原文地址:https://www.cnblogs.com/ffaiss/p/9842095.html
Copyright © 2011-2022 走看看