4.操作索引
与索引相关的操作主要涉及设置读写索引、mark和rest等。我们看下ByteBuf的markReaderIndex()方法:
public ByteBuf markReaderIndex() {
markedReaderIndex = readerIndex;
return this;
}
5.重用缓冲区
前面介绍功能的时候已经简单讲解了如何通过discardReadBytes和discardSomeReadBytes方法重用已经读取过的缓冲区,下面结合discardReadBytes方法的实现进行分析。
首先对读索引进行判断,如果为0说明没有可重用的缓冲区,直接返回。如果读索引大于0且读索引不等于写索引,说明缓冲区中既有已经读取过的被丢弃的缓冲区,也有尚未读取的可读缓冲区。调用setBytes(0,this,readerIndex,writerIndex-readerIndex)方法进行字节数组复制。将尚未读取的字节数组复制到缓冲区的起始位置,然后重新设置读写索引,读索引设置为0,写索引设置为之前的写索引减去读索引(重用的缓冲区长度)。
public ByteBuf discardReadBytes() {
ensureAccessible();
if (readerIndex == 0) {
return this;
}
if (readerIndex != writerIndex) {
setBytes(0, this, readerIndex, writerIndex - readerIndex);
writerIndex -= readerIndex;
adjustMarkers(readerIndex);
readerIndex = 0;
} else {
adjustMarkers(readerIndex);
writerIndex = readerIndex = 0;
}
return this;
}
在设置读写索引的同时,需要同时调整markedReaderIndex和markedWriterIndex。
首先对备份的markedReaderIndex和需要减少的decrement进行判断,如果小于需要减少的值,则将markedReaderIndex设置为0.注意,无论是markedReaderIndex还是markedwriterIndex,它的取值都不能小于0.如果markedWriterIndex也小于需要减少的值,则markedWriterIndex置为0,否则,markedWriterIndex减去decrement之后的值就是新的markedWriterIndex。
protected final void adjustMarkers(int decrement) {
int markedReaderIndex = this.markedReaderIndex;
if (markedReaderIndex <= decrement) {
this.markedReaderIndex = 0;
int markedWriterIndex = this.markedWriterIndex;
if (markedWriterIndex <= decrement) {
this.markedWriterIndex = 0;
} else {
this.markedWriterIndex = markedWriterIndex - decrement;
}
} else {
this.markedReaderIndex = markedReaderIndex - decrement;
markedWriterIndex -= decrement;
}
}
如果需要减小的值小于markedReaderIndex,则也一定小于markedWriterIndex,markedReaderIndex和markedWriterIndex的新值就是减去decrement之后的取值。
6.skipBytes
在解码的时候,有时候需要丢弃非法的数据报,或者跳跃过不需要读取的字节或字节数组,此时,使用skipBytes方法就非常方便。它可以忽略指定长度的字节数组,读操作时直接跳过这些数据读取后面的可读缓冲区,详细代码如下:
public ByteBuf skipBytes(int length) {
checkReadableBytes(length);
int newReaderIndex = readerIndex + length;
if (newReaderIndex > writerIndex) {
throw new IndexOutOfBoundsException(String.format(
"length: %d (expected: readerIndex(%d) + length <= writerIndex(%d))",
length, readerIndex, writerIndex));
}
readerIndex = newReaderIndex;
return this;
}
AbastractReferenceCountedByteBuf源码分析
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
private static final long REFCNT_FIELD_OFFSET;
static {
long refCntFieldOffset = -1;
try {
if (PlatformDependent.hasUnsafe()) {
refCntFieldOffset = PlatformDependent.objectFieldOffset(
AbstractReferenceCountedByteBuf.class.getDeclaredField("refCnt"));
}
} catch (Throwable t) {
// Ignored
}
REFCNT_FIELD_OFFSET = refCntFieldOffset;
}
@SuppressWarnings("FieldMayBeFinal")
private volatile int refCnt = 1;
首先看第一个字段refCntUpdater,它是AtomicIntegerFieldUpdater类型变量,通过原子的方式对成员变量进行更新等操作,以实现线程安全,清除锁。第二个字段是REFCNT_FIELD_OFFSET,它用于标示refCnt字段在AbstractReferenceCountedByteBuf中的内存地址。该内存地址的获取是JDK实现强相关的,如果使用SUN的JDK,它通过sum.mic.Unsafe的objectFieldObject接口来获得,ByteBuf的实现子类UnpooledUnsafeDirectByteBuf和PooledUnsafeDirectByteBuf会使用到这个偏移量。
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;
}
通过自旋对引用计数器进行加一操作,由于引用计数器的初始值为1,如果申请和释放操作能够保证正常使用,则它的最小值为1.当被释放和被申请的次数相等时,就调用回收方法回收当前的ByteBuf对象。如果为0,说明对象被意外、错误地引用,抛出IllegalReferenceCountException。如果引用计数达到整形的最大值,抛出越界的异常IllegalReferenceCountException。最后通过compareAndSet进行原子更新。
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源码分析
private final ByteBufAllocator alloc;
private byte[] array;
private ByteBuffer tmpNioBuf;
首先,它聚合了一个ByteBufAllocator,用于UnpooledHeapByteBuf的内存分配,紧接着定义了一个byte数组作为缓冲区,最后定义了一个ByteBuffer类型的tmpNioBuf变量用于实现Netty ByteBuf到JDK NIO ByteBuffer的转换。
public ByteBuf capacity(int newCapacity) {
ensureAccessible();
if (newCapacity < 0 || newCapacity > maxCapacity()) {
throw new IllegalArgumentException("newCapacity: " + newCapacity);
}
int oldCapacity = array.length;
if (newCapacity > oldCapacity) {
byte[] newArray = new byte[newCapacity];
System.arraycopy(array, 0, newArray, 0, array.length);
setArray(newArray);
} else if (newCapacity < oldCapacity) {
byte[] newArray = new byte[newCapacity];
int readerIndex = readerIndex();
if (readerIndex < newCapacity) {
int writerIndex = writerIndex();
if (writerIndex > newCapacity) {
writerIndex(writerIndex = newCapacity);
}
System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
} else {
setIndex(newCapacity, newCapacity);
}
setArray(newArray);
}
return this;
}
private void setArray(byte[] initialArray) {
array = initialArray;
tmpNioBuf = null;
}
public ByteBuffer nioBuffer(int index, int length) {
ensureAccessible();
return ByteBuffer.wrap(array, index, length).slice();
}
public static ByteBuffer wrap(byte[] array,
int offset, int length)
{
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}
由于UnpooledDerectByteBuf与UnpooledHeapByteBuf的实现原理相同,不同之处就是它内部缓冲区由java.nio.DirectByteBuffer实现。
PooledByteBuf内存池原理分析
final PooledByteBufAllocator parent;
private final int pageSize;
private final int maxOrder;
private final int pageShifts;
private final int chunkSize;
private final int subpageOverflowMask;
private final PoolSubpage<T>[] tinySubpagePools;
private final PoolSubpage<T>[] smallSubpagePools;
private final PoolChunkList<T> q050;
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;
2.PoolChunk
对于小于一个Page的内存,Netty在Page中完成分配。每个Page会被切分成大小相等的多个存储块,存储块的大小由第一次申请的内存块大小决定。假如一个Page是8个字节,如果第一次申请的块大小是4个字节,那么这个Page就包含2个存储块;如果第一次申请的是8个字节,那么这个Page就被分成1个存储块。
final PoolChunk<T> chunk;
final int memoryMapIdx;
final int runOffset;
final int pageSize;
final long[] bitmap;
PoolSubpage<T> prev;
PoolSubpage<T> next;
boolean doNotDestroy;
int elemSize;
int maxNumElems;
int nextAvail;
int bitmapLength;
int numAvail;
4.内存回收策略