架构师-Netty(五)

半腔热情 提交于 2020-02-05 18:47:49

Buffer和相关辅助类

ByteBuffer

常用的缓冲区JDK NIO 类库 java.nio.Buffer
在这里插入图片描述
JDK提供的ByteBuffer可以满足NIO编程,但有其局限性:

  • ByteBuffer 长度固定,不能自动扩缩容,编程对象POJO大于ByteBuffer的容量时,或发生索引越界异常
  • ByteBuffer只有一个标识位置的指针position,读写的时候需要手工调用flip()rewind()等,使用者必须必须小心谨慎地处理这些API,否则很容易导致程序处理失败;
  • ByteBuffer的API功能有限,一些高级和实用的特性不支持,需要使用者自己编程实现

ByteBuf自动扩容
iAbstractByteBuf#writeByte调用ensureWritable0方法

final void ensureWritable0(int minWritableBytes) {
       ensureAccessible();
       if (minWritableBytes <= writableBytes()) {
           return;
       }

       if (minWritableBytes > maxCapacity - writerIndex) {
           throw new IndexOutOfBoundsException(String.format(
                   "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
                   writerIndex, minWritableBytes, maxCapacity, this));
       }

       // Normalize the current capacity to the power of 2.
       //  自动扩缩容
       int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
       // Adjust to the new capacity.
       capacity(newCapacity);
   }

ByteBuf的工作原理

ButeBuf依然是个Byte数组的缓冲区,基本功能应该与JDK的ByteBuffer一致:

  • 7种Java基本类型、byte数组、ByteBuffer(ByteBuf)等的读写
  • 缓冲区自身的copy和slice等
  • 设置网络字节序
  • 构造缓冲区实例
  • 操作位置指针等方法

Netty ByteBuf是根据ByteBuffer实现,现有两种策略:

  • 参考JDK ByteBuffer的实现,增加额外的功能,解决原ByteBuffer的缺点
  • 聚合JDK ByteBuffer,通过Facade模式对其进行包装,可以减少自身代码量,降低实现成本

ByteBuffer的读写

在这里插入图片描述

ByteBuffer不做flip操作,读取到将是positioncapacity之间的错误内容

   ByteBuffer buffer = ByteBuffer.allocate(88);
   String value = "Hello World";
   buffer.put(value.getBytes());
   buffer.flip();
   byte[] vArray = new byte[buffer.remaining()];
   buffer.get(vArray);
   System.out.println(new String(vArray,"UTF-8"));

ByteBuf
顺序读操作readxxx 类似于ByteBuffer的get操作
顺序写操作writexxx 类似于ByteBuf的put操作
随机读写(set和get)

在这里插入图片描述

readIndex和writerIndex

  • ByteBuf通过两个位置指针协助缓冲区读写操作,
  • readIndex用于标识读取索引,调用ByteBuf的read操作,从readIndex处开始读
  • writerIndex用于标识写入索引,调用ByteBuf的write操作,从writerIndex处开始写
  • readIndexwriterIndex之间的空间为可读的字节缓冲区
  • writerIndex·到capacity`之间为可写的字节缓冲区

Readable bytes和Writable byes

可读空间端(0-writerIndex)是数据实际存储的区域
调用read或skip开头会从readerIndex开始读取或跳过指定的数据,操作完成后,readerIndex增加了读取或者跳过的字节数长度。越界抛出IndexOutOfBoundsException

可写空间段(writerIndex-capacity)是尚未使用可以填充的空闲空间
调用write开头操作会从writerIndex向空闲空间写入字节,writerIndex随之增加,越界抛出IndexOutOfBoundsException

discardable

0到readIndex之间是已经读取过的缓冲区,视为discard

调用discardReadBytes操作,可以释放这部分空间,类似ByteBuffer#compact方法,可重用之间已经读取过的缓冲区

discardReadBytes会发生字节数组的内存复制,频繁调用将会导致性能下降

clear

ByteBuf 调用clear操作会重置readerIndex和writerIndex 类似JDK ByteBuffer的clear操作

Mark和Reset

markReaderIndex 将当前的readerIndex备份到markedReaderIndex中;
resetReaderIndex 将当前的readerIndex设置为markedReaderIndex;
markerWriterIndex 将当前的writerIndex备份到markedWriterIndex
resetWriterIndex 将当前的writerIndex设置为markedWriterIndex

ByteBuf主要类继承关系

在这里插入图片描述
从内存分配角度看,ByteBuf可以分类

  • 堆内存(HeapByteBuf): 优点内存分配和回收快,可以被JVM自动回收;缺点:如果进行Socket的I/O读写,需要额外做一次内存复制,将堆内存对应的缓冲区复制到内核Channel中性能会有有一定程度的下降
  • 直接内存(DirectByteBuf)字节缓冲区: 非堆内存,它在堆外进行内存分配,相比与堆内存,它的分配和回收速度会慢一些,但SocketChannel的读写少一次内存复制

ByteBuf最佳实践在I/O通信线程读写缓冲区使用DirectByteBuf,后端业务编码模块使用HeadByteBuf

从内存回收角度分类:
pool基于对象池的ByteBuf和普通ByteBuf,对象池的管理和维护复杂

AbstractByteBuf
读写索引,mark,最大容量等公共属性方法定义

// 所有ByteBuf实例共享 ResourceLeakDetector用于检测对象是否泄漏
static final ResourceLeakDetector<ByteBuf> leakDetector =
            ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);
int readerIndex;
int writerIndex;
private int markedReaderIndex;
private int markedWriterIndex;
private int maxCapacity;

AbstractReferenceCountedByteBuf
对引用进行计数,类似于JVM内存回收对象引用计数器,用于跟踪对象的分配和销毁,做自动内存回收

...
// 通过原子方式对成员变量进行更新操作,线程安全,消除锁
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater;

static {
    AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> updater =
            PlatformDependent.newAtomicIntegerFieldUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
    if (updater == null) {
        updater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
    }
    refCntUpdater = updater;
}
// 跟踪对象的引用次数 volatile 解决多线程的可见性
private volatile int refCnt = 1;
... 
// 自旋 引用计算加一 cas操作
@Override
public ByteBuf retain() {
     for (;;) {
         int refCnt = this.refCnt;
         if (refCnt == 0) {
             throw new IllegalReferenceCountException(0, 1);
         }
         if (refCnt == Integer.MAX_VALUE) {
             throw new IllegalReferenceCountException(Integer.MAX_VALUE, 1);
         }
         if (refCntUpdater.compareAndSet(this, refCnt, refCnt + 1)) {
             break;
         }
     }
     return this;
 }
// 自旋 释放引用计数 
@Override
public final boolean release() {
    for (;;) {
        int refCnt = this.refCnt;
        if (refCnt == 0) {
            throw new IllegalReferenceCountException(0, -1);
        }

        if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) {
            if (refCnt == 1) {
                deallocate();
                return true;
            }
            return false;
        }
    }
}

UnpooledHeapByteBuf
基于堆内存分配字节缓冲区,没有基于对象池技术实现
``java
public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
//
private final ByteBufAllocator alloc;
private byte[] array;
private ByteBuffer tmpNioBuf;

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!