Jdk得Buffer的局限性
NIO缓冲区,Jdk提供的是Buffer类
可以看到7种基本类型数据都有具体的实现,那么为什么Netty还要提供自己的Buffer呢?
是因为Jdk提供的Buffer有其局限性。下面以ByteBuffer为例来说明:
- BtyeBuffer长度固定,不支持动态扩容,当数据大于ByteBuffer的容量的时候,会发生索引越界异常。
2. ByteBuffer只有一个位置指针position,读写的时候需要手动调用flip()rewind()等函数。
3. ByteBuffer的功能局限性大,一些高级的功能需要自己实现。
Netty的ByteBuf
ByteBuffer
Jdk的ByteBuffer只有一个位置指针,在读写操作的时候需要自行调用函数才能让程序正常运行。
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("Netty".getBytes());
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
System.out.println(new String(bytes));
上面是一段ByteBuffer写入数据和读取数据的程序,来看一下flip()操作前后的对比。
flip()之前
position在写入数据的位置,limit限制写入的大小。
flip()之后
position设置为0,limit=position,capacity不变,positon到limit之间是可读数据的内容。
ByteBuf
Netty的ByteBuf提供了两个指针,读指针readerIndex和写指针writerIndex,用来支持顺序的读操作和写操作。
readable bytes
readable bytes是实际存储数据的区域,以read或skip开头的任何操作都会读或跳过数据从当前的readindex,并且readindex会被增加为读取或跳过的字节数。如果读取操作的参数也是一个 ByteBuf,并且没有指定目标索引,那么指定的缓冲区的 writerIndex(写入索引)会一起增加。如果读取的字节长度大于实际可读的字节数,抛出数组越界异常。新分配、包装或复制的ByteeBuf readerIndex 的默认值为 0。
writable bytes
这个区域是一个尚未被使用的空间,任何以write开头的操作,都会增加writerIndex,根据写入的字节数。如果写操作的参数也是 ByteBuf,并且没有指定源索引,则指定缓冲区的readerIndex 会一起增加。
discardable bytes
这个区域的数据是已经被读操作读取了,起初这个区域的容量是0,读操作执行的时候长度会被增加,调用discardReadBytes()可以回收次区域。
调用discardReadBytes()之前
调用discardReadBytes()之后
clear()
clear之前
clear之后
ByteBuf的源码分析
主要实现类关系
从内存分配角度看,ByteBuf可以分为两类:
- 堆内存(HeapByteBuf)字节缓冲区:内存分配和回收快,可以被jvm自动回收;缺点是如果进行Socket的I/O读写,需要额外的做一次内存复制,将堆内存对应的缓冲区复制到内核Channel中,性能会有一定的下降。
- 直接内存(DirectByteBuf)字节缓冲区:在堆外进行分配,相比于堆内存分配和回收速度会比较慢,但是将它写入或者从Socket中读取时,由于少了一次内存复制,速度比堆内存快。
从内存回收角度看,ByteBuf也可以分为两类:基于对象池的ButeBuf和普通的ByteBuf。两者主要区别在于对象池的ByteBuf可以重用。
AbstractByteBuf
AbstractByteBuf继承ByteBuf,ByteBuf的抽象接口能力会在AbstractByteBuf中实现。
成员变量:读写指针,最大容量
在AbstractByteBuf中并没有定义ByteBuf缓冲区的实现,AbstractByteBuf实现了通用的功能,具体能力交由子类实现。
读操作
由于都操作的接口很多,不进行一一分析,重点分析readBytes(byte[] dst, int dstIndex, int length)接口
@Override
public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
checkReadableBytes(length);
getBytes(readerIndex, dst, dstIndex, length);
readerIndex += length;
return this;
}
在读取操作之前先进行缓冲区的校验
protected final void checkReadableBytes(int minimumReadableBytes) {
checkReadableBytes0(checkPositiveOrZero(minimumReadableBytes, "minimumReadableBytes"));
}
public static int checkPositiveOrZero(int i, String name) {
if (i < 0) {
throw new IllegalArgumentException(name + " : " + i + " (expected: >= 0)");
} else {
return i;
}
}
长度小于0抛IllegalArgumentException异常
private void checkReadableBytes0(int minimumReadableBytes) {
ensureAccessible();
if (checkBounds && readerIndex > writerIndex - minimumReadableBytes) {
throw new IndexOutOfBoundsException(String.format(
"readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
readerIndex, minimumReadableBytes, writerIndex, this));
}
}
可读的字节数不够读取的长度抛出IndexOutOfBoundsException异常。
校验完成后开始读取数据,从byte[] dst字节数组dstIndex开始,读取length长度的数据
写操作
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
ensureWritable(length);
setBytes(writerIndex, src, srcIndex, length);
writerIndex += length;
return this;
}
拿writeBytes(ByteBuf src, int srcIndex, int length)方法分析
@Override
public ByteBuf ensureWritable(int minWritableBytes) {
ensureWritable0(checkPositiveOrZero(minWritableBytes, "minWritableBytes"));
return this;
}
长度校验
public static int checkPositiveOrZero(int i, String name) {
if (i < 0) {
throw new IllegalArgumentException(name + " : " + i + " (expected: >= 0)");
} else {
return i;
}
}
final void ensureWritable0(int minWritableBytes) {
final int writerIndex = writerIndex();
final int targetCapacity = writerIndex + minWritableBytes;
// using non-short-circuit & to reduce branching - this is a hot path and targetCapacity should rarely overflow
if (targetCapacity >= 0 & targetCapacity <= capacity()) {
ensureAccessible();
return;
}
if (checkBounds && (targetCapacity < 0 || targetCapacity > maxCapacity)) {
ensureAccessible();
throw new IndexOutOfBoundsException(String.format(
"writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
writerIndex, minWritableBytes, maxCapacity, this));
}
// Normalize the target capacity to the power of 2.
final int fastWritable = maxFastWritableBytes();
int newCapacity = fastWritable >= minWritableBytes ? writerIndex + fastWritable
: alloc().calculateNewCapacity(targetCapacity, maxCapacity);
// Adjust to the new capacity.
capacity(newCapacity);
}
if (targetCapacity >= 0 & targetCapacity <= capacity()) {
ensureAccessible();
return;
}
targetCapacity >= 0 & targetCapacity <= capacity()容量满足直接返回
if (checkBounds && (targetCapacity < 0 || targetCapacity > maxCapacity)) {
ensureAccessible();
throw new IndexOutOfBoundsException(String.format(
"writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
writerIndex, minWritableBytes, maxCapacity, this));
}
targetCapacity < 0 || targetCapacity > maxCapacity 小于0或者大于最大容量抛出IndexOutOfBoundsException异常
int newCapacity = fastWritable >= minWritableBytes ? writerIndex + fastWritable
: alloc().calculateNewCapacity(targetCapacity, maxCapacity);
fastWritable >= minWritableBytes 可写最大字节数>=要写入的字节数,newCapacity就是写指针位置加上最大可写字节数,否则为2的幂
重用缓冲区
@Override
public ByteBuf discardReadBytes() {
if (readerIndex == 0) {
ensureAccessible();
return this;
}
if (readerIndex != writerIndex) {
setBytes(0, this, readerIndex, writerIndex - readerIndex);
writerIndex -= readerIndex;
adjustMarkers(readerIndex);
readerIndex = 0;
} else {
ensureAccessible();
adjustMarkers(readerIndex);
writerIndex = readerIndex = 0;
}
return this;
}
if (readerIndex == 0) {
ensureAccessible();
return this;
}
读索引为0,说明没有可重用的缓冲区,校验访问权限后返回
if (readerIndex != writerIndex) {
setBytes(0, this, readerIndex, writerIndex - readerIndex);
writerIndex -= readerIndex;
adjustMarkers(readerIndex);
readerIndex = 0;
}
readerIndex != writerIndex说明有被读过可以重用的缓冲区,也有未读的缓冲区数据。调用setBytes(0, this, readerIndex, writerIndex - readerIndex),将readerIndex到writerIndex之间的未读取的数据复制到0位置到writerIndex-readerIndex上。设置读写指针。
else {
ensureAccessible();
adjustMarkers(readerIndex);
writerIndex = readerIndex = 0;
}
没有可读的字节数组读写指针都指向0
AbstractReferenceCountedByteBuf
此类的作用是对引用进行计数,类似于垃圾回收的引用计数算法,做自动内存回收。
ReferenceCounted
ReferenceCounted类定义了引用计数的接口,在Netty中具备引用计数能力的类都实现了此类。当一个新的ReferenceCounted类被创建时,引用计数从0开始。retain()方法增加引用计数,release()方法减小引用计数。如果引用计数减小到0,对象将会被释放,访问被释放的对象将导致违规访问。
一个实现ReferenceCounted对象是其他实现ReferenceCounted对象的容器,容器对象释放了,容器里的对象也会被释放。
方法介绍:
- int refCnt(),返回此引用对象的计数,如果计数是0,这个对象会被回收
- ReferenceCounted retain(),增加引用计数
- ReferenceCounted retain(int increment);,增加指定的引用计数
- ReferenceCounted touch(),记录此对象的当前访问位置,以便进行调试。如果确定该对象为泄露,该操作记录的信息将通过 {@link ResourceLeakDetector} 提供给你。此方法是 {@link touch(Object) touch(null)} 的快捷方式。
- ReferenceCounted touch(Object hint),记录此对象的当前访问位置,并附加任意信息以进行调试。如果确定该对象为泄露,该操作记录的信息将通过 {@link ResourceLeakDetector} 提供给你。
- boolean release(),减小引用计数为1,并且计数为0时释放对象。引用数量为0时返回true
- boolean release(int decrement),减小指定的引用计数数量。引用数量为0时返回true
ReferenceCountUpdater
是ReferenceCounted的通用实现类
/*
* Implementation notes:
*
* For the updated int field:
* Even => "real" refcount is (refCnt >>> 1)
* Odd => "real" refcount is 0
*
* (x & y) appears to be surprisingly expensive relative to (x == y). Thus this class uses
* a fast-path in some places for most common low values when checking for live (even) refcounts,
* for example: if (rawCnt == 2 || rawCnt == 4 || (rawCnt & 1) == 0) { ...
*/
先来简单理解下源码中上面的注释,这对理解整个流程有很大的帮助
For the updated int field:
* Even => "real" refcount is (refCnt >>> 1)
* Odd => "real" refcount is 0
- 偶数的引用计数是refcnt>>>1,refcnt类似于 8>>>1=4,4>>>1=2,2>>>1;奇数的引用计数为0说明就是要被回收了。所以整个逻辑可以理解为,引用计数是偶数的时候代表有被使用不能回收,为奇数的时候没被使用要被回收,是不是要释放判断奇偶性就可以了。
(x & y) appears to be surprisingly expensive relative to (x == y). Thus this class uses
* a fast-path in some places for most common low values when checking for live (even) refcounts,
* for example: if (rawCnt == 2 || rawCnt == 4 || (rawCnt & 1) == 0) { ...
(x & y)没有(x == y)的效率高
- 初始时引用计数的值是2,retain()后 2<<1 为4,releanse()后4>>>1为2。
UnpooledHeapByteBuf
基于堆内存的字节缓冲区,没有基于对象池去实现,每次操作都会创建一个新的UnpooledHeapByteBuf.
- 扩容
public ByteBuf capacity(int newCapacity) {
checkNewCapacity(newCapacity);
byte[] oldArray = array;
int oldCapacity = oldArray.length;
if (newCapacity == oldCapacity) {
return this;
}
int bytesToCopy;
if (newCapacity > oldCapacity) {
bytesToCopy = oldCapacity;
} else {
trimIndicesToCapacity(newCapacity);
bytesToCopy = newCapacity;
}
byte[] newArray = allocateArray(newCapacity);
System.arraycopy(oldArray, 0, newArray, 0, bytesToCopy);
setArray(newArray);
freeArray(oldArray);
return this;
}
newCapacity == oldCapacity新的容量和老的容量相等返回。新的容量大于老的容量,说明老的数据都要被拷贝到新的缓冲区中,否则(else逻辑),writerIndex() > newCapacity写指针大于新的容量,setIndex0(Math.min(readerIndex(), newCapacity), newCapacity)设置读写指针。byte[] newArray = allocateArray(newCapacity)创建新的字节数组,System.arraycopy(oldArray, 0, newArray, 0, bytesToCopy)数组拷贝,setArray(newArray)设置新的数组。
- 字节数组复制
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
checkSrcIndex(index, length, srcIndex, src.length);
System.arraycopy(src, srcIndex, array, index, length);
return this;
}
checkSrcIndex(index, length, srcIndex, src.length)校验,System.arraycopy(src, srcIndex, array, index, length)数组拷贝