一、ByteBuf简介
下面的简介都来源于java doc。
ByteBuf是随机可访问且顺序可访问的字节序列。为byte数组和ByteBuffer提供抽象视图。
1、创建Buffer
推荐使用Unpooled,不要直接使用构造方法
2、随机访问
和普通原始字节数组一样,通过下标(0~容量)支持随机访问
3、顺序访问
通过readerIndex和writerIndex支持顺序访问
由readerIndex和writerIndex将buffer划分为三块:
- discardable bytes:可丢弃的字节
- readable bytes:可读的字节
- writable bytes:可写的字节
相关方法:
- readerIndex()/writerIndex():获取读写下标
- readerIndex(int readerIndex)/writerIndex(int writerIndex):设置读写下标
- discardReadBytes:丢弃discardable bytes
- clear:重置读写index为0
4、搜索
- 单字节搜索
- indexOf(int, int, byte)
- bytesBefore(int, int, byte)
- bytesBefore(byte)
- 复杂搜索
- forEachByte(int, int, ByteProcessor)
5、标记和重置
读写index可以标记和重置
- markReaderIndex()/markWriterIndex():标记当前index
- resetReaderIndex()/resetWriterIndex():重置当前index为之前标记的index
6、衍生Buffer
- 视图拷贝:底层数据存储是一份,只是复制一份视图,读写指针两个对象互不干涉
- 无引用计数
- duplicate() 不会修改当前ByteBuf的读写index
- slice() 不会修改当前ByteBuf的读写index
- slice(int, int) 不会修改当前ByteBuf的读写index
- readSlice(int) 会修改当前ByteBuf读index
- 有引用计数:相对于无引用计数,可能会产生更少垃圾,当引用计数为0时,直接内存会主动释放,使用堆内存会将字节数组置空
- retainedDuplicate()
- retainedSlice()
- retainedSlice(int, int)
- readRetainedSlice(int)
- 无引用计数
- 完全拷贝:一个全新的ByteBuf,内容与老ByteBuf一致,底层数据存储是两份
- copy()
7、JDK类型转换
- 字节数组转换:hasArray()方法判断底层是否是字节数组支持,array()方法返回字节数组,对于直接内存创建的缓冲区hasArray()返回false
- 字符串转换:toString(Charset)负责ByteBuf转换为String,注意toString()方法不是转换方法
- IO流转换:ByteBufInputStream和ByteBufOutputStream
二、ByteBuf继承关系
这里重点挑选一些有代表性的接口和类做讨论。部分已废弃的接口和类不做讨论。
1、ReferenceCounted
A reference-counted object that requires explicit deallocation.
实现ReferenceCounted接口代表这个对象具有引用计数功能,但是需要显示释放对象。言外之意,这里只是定义了一个引用计数对象的计数器操作方法,实际释放对象占用资源,需要客户端自己显示调用。比如release减少计数到0,不一定是在release方法中做资源释放,需要客户端自己释放。
If the reference count is decreased to 0, the object will be deallocated explicitly, and accessing the deallocated object will usually result in an access violation
如果引用计数降至0,需要显示释放,并且使用释放后的对象会导致访问冲突。
接口定义如下,这里省略了一些同样含义的快捷方法(如retain的无参方法)。
public interface ReferenceCounted {
// 返回引用计数
int refCnt();
// 增加引用计数
ReferenceCounted retain(int increment);
// 减少引用计数
boolean release(int decrement);
// 记录当前实例,用以内存泄露分析,hint入参只是为了debug的额外信息
ReferenceCounted touch(Object hint);
}
2、WrappedByteBuf
一个简单的包装类,所有ByteBuf的抽象方法,都委托实际包装的ByteBuf实现,代码省略。
class WrappedByteBuf extends ByteBuf {
protected final ByteBuf buf;
protected WrappedByteBuf(ByteBuf buf) {
this.buf = ObjectUtil.checkNotNull(buf, "buf");
}
}
3、EmptyByteBuf
空ByteBuf,大部分方法抛出IndexOutOfBoundsException,容量capcity和最大容量maxCapacity都为0。
部分代码如下。
public final class EmptyByteBuf extends ByteBuf {
// 当前容量
@Override
public int capacity() {
return 0;
}
// 扩容
@Override
public ByteBuf capacity(int newCapacity) {
throw new ReadOnlyBufferException();
}
// 最大容量
@Override
public int maxCapacity() {
return 0;
}
// 读
@Override
public byte getByte(int index) {
throw new IndexOutOfBoundsException();
}
// 写
@Override
public ByteBuf writeByte(int value) {
throw new IndexOutOfBoundsException();
}
}
4、AbstractByteBuf
AbstractByteBuf是ByteBuf的骨架实现。
成员变量
注意到capacity当前容量,并不是AbstractByteBuf的成员变量,capacity方法获取当前容量延迟到了子类实现。
public abstract class AbstractByteBuf extends ByteBuf {
// 是否校验引用计数为0时的ByteBuf访问,默认true
static final boolean checkAccessible;
// 是否做下标校验,如readIndex是否超过writeIndex,writeIndex是否超过capacity,默认true
private static final boolean checkBounds;
// 泄露检测
static final ResourceLeakDetector<ByteBuf> leakDetector =
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);
// 读下标
int readerIndex;
// 写下标
int writerIndex;
// 标记读下标
private int markedReaderIndex;
// 标记写下标
private int markedWriterIndex;
// 最大容量
private int maxCapacity;
}
代表性方法
index相关操作
// 查询读index
@Override
public int readerIndex() {
return readerIndex;
}
// 设置读index
@Override
public ByteBuf readerIndex(int readerIndex) {
// 校验下标是否溢出
if (checkBounds) {
checkIndexBounds(readerIndex, writerIndex, capacity());
}
this.readerIndex = readerIndex;
return this;
}
// 是否可读
@Override
public boolean isReadable() {
return writerIndex > readerIndex;
}
// 可读字节
@Override
public int readableBytes() {
return writerIndex - readerIndex;
}
// 清空读写下标
@Override
public ByteBuf clear() {
readerIndex = writerIndex = 0;
return this;
}
// 标记当前读index
@Override
public ByteBuf markReaderIndex() {
markedReaderIndex = readerIndex;
return this;
}
// 重置读index到标记位置
@Override
public ByteBuf resetReaderIndex() {
readerIndex(markedReaderIndex);
return this;
}
// 获取index位置的字节数据
@Override
public byte getByte(int index) {
// 校验下标是否溢出
checkIndex(index);
// 子类实现实际逻辑
return _getByte(index);
}
protected abstract byte _getByte(int index);
// 读取当前readIndex位置的字节
@Override
public byte readByte() {
checkReadableBytes0(1);
int i = readerIndex;
// 子类实现
byte b = _getByte(i);
readerIndex = i + 1;
return b;
}
创建衍生buffer
视图拷贝。注意这里duplicate和slice实例化了AbstractDerivedByteBuf (非池化衍生Buffer)的子类返回。
// 整个ByteBuf的视图拷贝,构造UnpooledDuplicatedByteBuf
@Override
public ByteBuf duplicate() {
ensureAccessible();
return new UnpooledDuplicatedByteBuf(this);
}
// 在duplicate的基础上调用retain增加引用计数
@Override
public ByteBuf retainedDuplicate() {
return duplicate().retain();
}
// 切片视图拷贝,构造UnpooledSlicedByteBuf
@Override
public ByteBuf slice(int index, int length) {
ensureAccessible();
return new UnpooledSlicedByteBuf(this, index, length);
}
// 在slice的基础上调用retain增加引用计数
@Override
public ByteBuf retainedSlice(int index, int length) {
return slice(index, length).retain();
}
复制
@Override
public ByteBuf copy() {
// copy(int index, int length)方法由子类实现
return copy(readerIndex, readableBytes());
}
JDK类型转换
AbstractByteBuf没有实现byte数组转换(array方法),只实现了字符串转换。
@Override
public String toString(Charset charset) {
return toString(readerIndex, readableBytes(), charset);
}
@Override
public String toString(int index, int length, Charset charset) {
return ByteBufUtil.decodeString(this, index, length, charset);
}
搜索
顺序搜索
@Override
public int indexOf(int fromIndex, int toIndex, byte value) {
if (fromIndex <= toIndex) {
return firstIndexOf(fromIndex, toIndex, value);
} else {
return lastIndexOf(fromIndex, toIndex, value);
}
}
// 简单的顺序搜索,找不到指定value返回-1
private int firstIndexOf(int fromIndex, int toIndex, byte value) {
fromIndex = Math.max(fromIndex, 0);
if (fromIndex >= toIndex || capacity() == 0) {
return -1;
}
checkIndex(fromIndex, toIndex - fromIndex);
for (int i = fromIndex; i < toIndex; i ++) {
if (_getByte(i) == value) {
return i;
}
}
return -1;
}
复杂搜索
// index:起始下标
// length:搜索长度
// ByteProcessor:一个搜索处理器
@Override
public int forEachByte(int index, int length, ByteProcessor processor) {
checkIndex(index, length);
return forEachByteAsc0(index, index + length, processor);
}
int forEachByteAsc0(int start, int end, ByteProcessor processor) throws Exception {
for (; start < end; ++start) {
// 搜索处理器判断start位置的字节是否需要停止搜索
if (!processor.process(_getByte(start))) {
return start;
}
}
return -1;
}
复杂搜索案例:
@Test
public void test02() {
ByteBuf buffer = Unpooled.buffer(1024);
buffer.writeByte('c').writeByte('a')
.writeByte('d').writeByte(4).writeByte(' ').writeByte(5);
// FIND_ASCII_SPACE搜索到空格时process方法返回false停止
int i = buffer.forEachByte(ByteProcessor.FIND_ASCII_SPACE);
System.out.println(i);// 下标为4
}
5、AbstractReferenceCountedByteBuf
AbstractReferenceCountedByteBuf实现了ReferenceCounted接口的所有方法,即引用计数。
成员变量
public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
// refCnt字段的内存地址偏移量
private static final long REFCNT_FIELD_OFFSET =
ReferenceCountUpdater.getUnsafeOffset(AbstractReferenceCountedByteBuf.class, "refCnt");
// refCnt字段的原子更新器
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> AIF_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
// 委托ReferenceCountUpdater实现ReferenceCounted
private static final ReferenceCountUpdater<AbstractReferenceCountedByteBuf> updater = new ReferenceCountUpdater<AbstractReferenceCountedByteBuf>() {
@Override
protected AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> updater() {
return AIF_UPDATER;
}
@Override
protected long unsafeOffset() {
return REFCNT_FIELD_OFFSET;
}
};
// 初始虚拟引用计数为2
private volatile int refCnt = updater.initialValue();
}
看到AbstractReferenceCountedByteBuf的成员变量主要是两个。
updater,ReferenceCountUpdater实例。ReferenceCounted接口的所有方法实现基本都是委托ReferenceCountUpdater实现原子更新的,只不过ReferenceCountUpdater是个抽象类,需要子类传入AtomicIntegerFieldUpdater原子更新器和unsafeOffset引用计数字段偏移量。
另一个是refCnt,记录了当前引用计数的虚拟值,这个虚拟值为2,实际引用次数是1。具体逻辑需要看ReferenceCountUpdater的实现。ReferenceCountUpdater每增加1个引用计数,refCnt会增加2。至于为什么不用实际引用次数累加,在后面讲。
关键方法
增加计数
@Override
public ByteBuf retain(int increment) {
return updater.retain(this, increment);
}
减少计数
首先调用ReferenceCountUpdater对refCnt做原子更新。
@Override
public boolean release(int decrement) {
return handleRelease(updater.release(this, decrement));
}
然后执行handleRelease方法,实际释放资源(回收当前ByteBuf),比如池化ByteBuf归还到池中,非池化ByteBuf直接释放,非池化HeapByteBuf把底层byte数组赋值为空数组,非池化DirectByteBuf把JDK的ByteBuffer释放。池化ByteBuf释放的逻辑会重点关注。
private boolean handleRelease(boolean result) {
if (result) {
deallocate();
}
return result;
}
/**
* Called once {@link #refCnt()} is equals 0.
*/
protected abstract void deallocate();
获取计数
@Override
public int refCnt() {
return updater.refCnt(this);
}
当前对象是否可访问
@Override
boolean isAccessible() {
return updater.isLiveNonVolatile(this);
}
6、非池化Buffer
UnpooledHeapByteBuf
UnpooledHeapByteBuf是非池化的ByteBuf,底层使用字节数组存储数据。
成员变量
public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
// 分配器负责创建ByteBuf
private final ByteBufAllocator alloc;
// 底层字节数组
byte[] array;
// JDK ByteBuffer,用于适配JDK相关API
// 底层还是会使用上面的array存储数据
// 这里特意创建一个成员变量来存储是为了减少new对象次数
private ByteBuffer tmpNioBuf;
}
核心方法
internalNioBuffer
部分JDK的API需要使用到原生的ByteBuffer,所以这里需要提供一个将byte数组封装为ByteBuffer的方法,且可以作为成员变量反复使用,减少new对象次数,同时也减少了垃圾回收次数。可以看到调用了ByteBuffer的wrap方法创建,底层存储数据还是用的成员变量array字节数组。
private ByteBuffer internalNioBuffer() {
ByteBuffer tmpNioBuf = this.tmpNioBuf;
if (tmpNioBuf == null) {
this.tmpNioBuf = tmpNioBuf = ByteBuffer.wrap(array);
}
return tmpNioBuf;
}
setBytes
比如对于下面这个方法,需要将FileChannel写入当前UnpooledHeapByteBuf,但是没有api可以直接写入字节数组,所以要用到JDK的ByteBuffer帮助写入底层array。每次通过internalNioBuffer获取缓存ByteBuffer时,使用前都需要通过clear清空原始的各种下标。
@Override
public int setBytes(int index, FileChannel in, long position, int length) throws IOException {
return in.read((ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length), position);
}
对于下面这个方法,就不需要使用ByteBuffer,因为有api支持直接写入字节数组。
@Override
public int setBytes(int index, InputStream in, int length) throws IOException {
return in.read(array, index, length);
}
deallocate 因为UnpooledHeapByteBuf继承了AbstractReferenceCountedByteBuf,需要实现deallocate方法负责释放底层资源,这里将成员变量array这个字节数组置为空数组。
@Override
protected void deallocate() {
array = EmptyArrays.EMPTY_BYTES;
}
UnpooledDirectByteBuf
UnpooledDirectByteBuf是非池化的ByteBuf,底层使用JDK的ByteBuffer存储数据,并且使用直接内存。
成员变量
public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf {
// 分配器 分配资源创建ByteBuf
private final ByteBufAllocator alloc;
// 底层是JDKByteBuffer来存储数据
ByteBuffer buffer; // accessed by UnpooledUnsafeNoCleanerDirectByteBuf.reallocateDirect()
// 一个临时的ByteBuffer,底层是由buffer.duplicate复制而来
// 仅仅是为了减少垃圾回收,重复利用对象
private ByteBuffer tmpNioBuf;
// 当前容量 初始值为底层ByteBuffer的limit - position
private int capacity;
// 一个标志位,标记是否未释放ByteBuffer
private boolean doNotFree;
}
构造方法
正常构造一个新的UnpooledDirectByteBuf。
public UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
// 设置最大容量
super(maxCapacity);
// 设置分配器
this.alloc = alloc;
// 设置this.buffer this.tmpNioBuf capacity
setByteBuffer(allocateDirect(initialCapacity), false);
}
void setByteBuffer(ByteBuffer buffer, boolean tryFree) {
// 尝试释放老的buffer直接内存,正常构造时用不到,只有扩容时会用到
if (tryFree) {
ByteBuffer oldBuffer = this.buffer;
if (oldBuffer != null) {
if (doNotFree) {
doNotFree = false;
} else {
// 释放直接内存
// PlatformDependent.freeDirectBuffer(buffer)
freeDirect(oldBuffer);
}
}
}
// 设置成员变量的初始值
this.buffer = buffer;
tmpNioBuf = null;
// capacity = JDK buffer的limit - position
capacity = buffer.remaining();
}
用已经存在的一个ByteBuffer作为底层buffer,创建UnpooledDirectByteBuf。
UnpooledDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer,
int maxCapacity, boolean doFree, boolean slice) {
super(maxCapacity);
int initialCapacity = initialBuffer.remaining();
this.alloc = alloc;
doNotFree = !doFree;
// slice为true则复制initialBuffer一份视图,保证指针独立,且position=0 limit和capacity为原来剩余字节数
// 仍然调用setByteBuffer方法,初始化this.buffer this.tmpNioBuf capacity
setByteBuffer((slice ? initialBuffer.slice() : initialBuffer).order(ByteOrder.BIG_ENDIAN), false);
// 用initialBuffer.remaining(),即limit-position作为writeIndex
writerIndex(initialCapacity);
}
核心方法
比较常用的读写buffer的方法就不放了,基本是操作数组和ByteBuffer的API。
internalNioBuffer
为了减少new对象次数,和垃圾回收次数,这里和UnpooledHeapByteBuf一样,会创建缓存的ByteBuffer,底层使用的ByteBuffer还是成员变量buffer。
// 复制一个ByteBuffer与buffer具有相同内容,但是index独立
// 初始index等同于原始buffer的index
private ByteBuffer internalNioBuffer() {
ByteBuffer tmpNioBuf = this.tmpNioBuf;
if (tmpNioBuf == null) {
this.tmpNioBuf = tmpNioBuf = buffer.duplicate();
}
return tmpNioBuf;
}
deallocate
deallocate在实际引用计数归零时,会被父类触发,这里会设置成员变量buffer为null,但是UnpooledDirectByteBuf并不一定会直接释放底层ByteBuffer,是否释放取决于doNotFree标志位。当前buffer可能仍然在别的地方被使用,比如当前UnpooledDirectByteBuf是通过已经存在的一个ByteBuffer创建的(见第二个构造方法),可能当前buffer还不能被释放。
@Override
protected void deallocate() {
ByteBuffer buffer = this.buffer;
if (buffer == null) {
return;
}
this.buffer = null;
if (!doNotFree) {
freeDirect(buffer);
}
}
三、ReferenceCountUpdater
引用计数更新器,辅助实现ReferenceCounted接口的引用计数功能。先来看一下文档注释。
Implementation notes:
For the updated int field:
Even => "real" refcount is (refCnt >>> 1)
Odd => "real" refcount is 0
等待更新的int成员变量(refCnt),如果是偶数,实际引用次数=refCnt/2;如果是奇数,实际引用次数=0。为什么要用2的倍数来做计数,直接用实际的引用次数做计数不行吗?具体原因稍候给出。
(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) { ...
由于==操作比&操作性能更好,所以有些地方会有这样的实现,比如if (rawCnt == 2 || rawCnt == 4 || (rawCnt & 1) == 0)。相当于引用1到2次的对象可以通过==判断出来,大于2次或奇数要通过&判断出来。
初始引用次数
初始raw引用次数为2,代表有一个引用。
public final int initialValue() {
return 2;
}
获取引用次数
获取实际引用次数,如果是偶数返回raw引用次数/2,如果是奇数返回0。
private static int realRefCnt(int rawCnt) {
return rawCnt != 2 && rawCnt != 4 && (rawCnt & 1) != 0 ? 0 : rawCnt >>> 1;
}
获取实际引用次数,返回值同realRefCnt,只不过如果是奇数会抛出异常。
private static int toLiveRealRefCnt(int rawCnt, int decrement) {
if (rawCnt == 2 || rawCnt == 4 || (rawCnt & 1) == 0) {
return rawCnt >>> 1;
}
throw new IllegalReferenceCountException(0, -decrement);
}
通过unsafe+offset,非volatile获取rawCnt。
private int nonVolatileRawCnt(T instance) {
final long offset = unsafeOffset();
return offset != -1 ? PlatformDependent.getInt(instance, offset) : updater().get(instance);
}
通过unsafe+offset,volatile获取rawCnt,转换为realCnt。
public final int refCnt(T instance) {
return realRefCnt(updater().get(instance));
}
通过unsafe+offset,非volatile获取rawCnt,返回对象是否仍然存在引用(rawCnt为偶数)。
public final boolean isLiveNonVolatile(T instance) {
final long offset = unsafeOffset();
final int rawCnt = offset != -1 ? PlatformDependent.getInt(instance, offset) : updater().get(instance);
return rawCnt == 2 || rawCnt == 4 || rawCnt == 6 || rawCnt == 8 || (rawCnt & 1) == 0;
}
增加引用次数
增加一次引用次数,在内部存储的时候会乘以2转换为rawCnt。
public final T retain(T instance, int increment) {
// 实际累加值 = increment * 2
int rawIncrement = checkPositive(increment, "increment") << 1;
return retain0(instance, increment, rawIncrement);
}
private T retain0(T instance, final int increment, final int rawIncrement) {
// 子类实现updater,原子累加
int oldRef = updater().getAndAdd(instance, rawIncrement);
// 如果老值不是2的倍数(偶数),表示实际引用次数为0,抛出异常
if (oldRef != 2 && oldRef != 4 && (oldRef & 1) != 0) {
throw new IllegalReferenceCountException(0, increment);
}
// getAndAdd超过Integer.MAX_VALUE溢出,需要回滚
if ((oldRef <= 0 && oldRef + rawIncrement >= 0)
|| (oldRef >= 0 && oldRef + rawIncrement < oldRef)) {
updater().getAndAdd(instance, -rawIncrement);
throw new IllegalReferenceCountException(realRefCnt(oldRef), increment);
}
return instance;
}
减少引用次数
注意这里返回boolean值,代表对象是否实际引用计数为0,需要被回收。
public final boolean release(T instance, int decrement) {
// 采用非volatile方式,获取当前raw引用次数
int rawCnt = nonVolatileRawCnt(instance);
// 获取实际引用次数
int realCnt = toLiveRealRefCnt(rawCnt, checkPositive(decrement, "decrement"));
return decrement == realCnt
// 如果扣减引用次数等于实际引用次数,尝试设置rawCnt为奇数1,如果cas失败,无线循环CAS修改rawCnt
? tryFinalRelease0(instance, rawCnt) || retryRelease0(instance, decrement)
// 否则cas修改rawCnt = rawCnt - decrement * 2
: nonFinalRelease0(instance, decrement, rawCnt, realCnt);
}
首先获取到当前的实际引用次数和raw引用次数,然后判断实际引用次数与待扣减次数是否相等。
如果相等,执行tryFinalRelease0方法。先尝试将raw引用次数改为1(任何奇数都认为该对象已被回收,这里写死的1)。
private boolean tryFinalRelease0(T instance, int expectRawCnt) {
return updater().compareAndSet(instance, expectRawCnt, 1);
}
如果tryFinalRelease0方法CAS失败,无限循环执行CAS,区别是读取volatile的rawCnt,再次尝试修改raw引用次数。
private boolean retryRelease0(T instance, int decrement) {
for (;;) {
// rawCnt读取是volatile读取,见AtomicIntegerFieldUpdaterImpl.get
int rawCnt = updater().get(instance), realCnt = toLiveRealRefCnt(rawCnt, decrement);
if (decrement == realCnt) {
// 如果扣减引用次数等于实际引用次数,尝试设置rawCnt为奇数1
if (tryFinalRelease0(instance, rawCnt)) {
// 返回true表示对象需要被回收
return true;
}
} else if (decrement < realCnt) {
// all changes to the raw count are 2x the "real" change
// 如果扣减引用次数小于实际引用次数,修改rawCnt = rawCnt - decrement * 2
if (updater().compareAndSet(instance, rawCnt, rawCnt - (decrement << 1))) {
return false;
}
} else {
// 如果扣减引用次数大于实际引用次数,抛出异常
throw new IllegalReferenceCountException(realCnt, -decrement);
}
// 这有利于高竞争下的吞吐量
Thread.yield(); // this benefits throughput under high contention
}
}
如果不相等,执行nonFinalRelease0方法,cas扣减raw引用次数,如果扣减成功返回false(表示当前对象不需要回收),否则一样执行上面的retryRelease0。
private boolean nonFinalRelease0(T instance, int decrement, int rawCnt, int realCnt) {
// cas修改rawCnt = rawCnt - decrement * 2
if (decrement < realCnt
&& updater().compareAndSet(instance, rawCnt, rawCnt - (decrement << 1))) {
return false;
}
return retryRelease0(instance, decrement);
}
为什么要用2的倍数作为引用计数?
因为某次性能优化,把retain0方法中的CAS修改引用计数,改为了getAndAdd,这样性能更好。但是随之而来的就是一个bug,当多线程操作同一个buffer的引用计数时,会发生问题。
考虑场景:一个object的refCnt为1。有3个线程修改这个object的引用计数。
- 线程1调用obj.release(),修改refCnt为0,此时obj应该被进一步执行deallocate。
- 线程2调用obj.retain(),修改refCnt为1,发现oldRef为0,然后去进行回滚getAndAdd(-1)并抛出异常。
- 由于上一步的修改和回滚是两步操作,并不是原子的。所以在回滚前refCnt还是1,此时线程3调用obj.retain(),修改refCnt为2,oldRef为1,线程3认为这是一个存活的对象,并不会抛出异常。
- 线程1调用obj.deallocate(),释放了obj。
经过修复后,一个object的refCnt初始值为2,代表有一个引用。有3个线程修改这个object的引用计数。
- 线程1调用obj.release(),修改refCnt为奇数,实现里是修改为1,此时obj应该被进一步执行deallocate。
- 线程2调用obj.retain(),修改refCnt为3,发现oldRef为1是奇数,直接抛出异常。
- 此时线程3调用obj.retain(),修改refCnt为5,发现oldRef为3是奇数,直接抛出异常。
参考github下面的三篇文章:
- Possible race condition on AbstractReferenceCounted
- 因为某次性能优化,将AbstractReferenceCounted更新计数的方式从compareAndSet改为getAndAdd,导致了并发问题。然后在AbstractReferenceCountedByteBuf里修复了这个问题。
- Centralize internal reference counting logic
- 这次提交是将更新计数的逻辑集中放入ReferenceCountUpdater。
- AbstractReferenceCountedByteBuf引用计数实现的一些变化
- 这篇中文文章综合了上面两篇文章,阐述了自增计数会造成什么问题。
此外为什么是用2的倍数,不是3的倍数或者其他,原因应该是2的倍数用位运算方便。