參考計數是一種常用的記憶體管理機制,是指將資源的被參考次數保存起來,當被參考次數變為零時就將其釋放的程序,Netty在4.x版本開始使用參考計數機制進行部分物件的管理,其實作思路并不是特別復雜,它主要涉及跟蹤某個物件被參考的次數,在Netty具體代碼中需要通過參考計數進行記憶體管理的物件,會基于ReferenceCounted介面實作,其中參考計數大于0時則代表該物件被參考不會釋放,當參考計數減少到0時,該物件就會被釋放,通過參考計數機制,Netty可以很好的實作記憶體管理,參考計數減少到0時要么直接釋放記憶體,要么放回記憶體池中重復利用,
1、基本示例
下面先通過一個簡單示例看下Netty中參考計數機制的使用
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf recvBuffer = (ByteBuf) msg;// 申請ByteBuf 需要主動釋放 if(recvBuffer.isDirect()){ System.err.println(true); } PooledByteBufAllocator allocator = new PooledByteBufAllocator(true); ByteBuf sendBuffer = allocator.buffer();//申請池化直接記憶體 System.err.println("sendBuffer的參考計數:"+sendBuffer.refCnt()); sendBuffer.retain(); System.err.println("sendBuffer的參考計數:"+sendBuffer.refCnt()); sendBuffer.release(); System.err.println("sendBuffer的參考計數:"+sendBuffer.refCnt());
try { byte[] bytesReady = new byte[recvBuffer.readableBytes()]; recvBuffer.readBytes(bytesReady); System.out.println("channelRead收到資料:"+ BytesUtils.toHexString(bytesReady)); byte[] sendBytes = new byte[] {0x7E,0x01,0x02,0x7e}; sendBuffer.writeBytes(sendBytes); ctx.writeAndFlush(sendBuffer); System.err.println("sendBuffer的參考計數:"+sendBuffer.refCnt()); }catch (Exception e) { // TODO: handle exception System.err.println(e.getMessage()); }finally { System.err.println("recvBuffer的參考計數:"+recvBuffer.refCnt()); recvBuffer.release(); //此處需要釋放 System.err.println("recvBuffer的參考計數:"+recvBuffer.refCnt()); } }
輸出結果如下,通過示例可以看出retain方法會增加計數參考,release方法會減少計數參考
true sendBuffer的參考計數:1 sendBuffer的參考計數:2 sendBuffer的參考計數:1 sendBuffer的參考計數:0 recvBuffer的參考計數:1 recvBuffer的參考計數:0
AbstractReferenceCountedByteBuf實作了對ByteBuf的記憶體管理,以實作記憶體的回收、釋放或者重復利用 ,AbstractReferenceCountedByteBuf的繼承實作關系如下圖所示

2、ReferenceCounted介面定義
首先是ReferenceCounted介面的定義
public interface ReferenceCounted { /** * Returns the reference count of this object. If {@code 0}, it means this object has been deallocated. * 回傳物件的參考計數 */ int refCnt(); /** * Increases the reference count by {@code 1}. * 增加參考計數 */ ReferenceCounted retain(); /** * Increases the reference count by the specified {@code increment}. * 參考計數增加指定值 */ ReferenceCounted retain(int increment); /** * Records the current access location of this object for debugging purposes. * If this object is determined to be leaked, the information recorded by this operation will be provided to you * via {@link ResourceLeakDetector}. This method is a shortcut to {@link #touch(Object) touch(null)}. * 記錄該物件的當前訪問位置,用于除錯, * 如果確定該物件被泄露,將提供此操作記錄的資訊給您 */ ReferenceCounted touch(); /** * Records the current access location of this object with an additional arbitrary information for debugging * purposes. If this object is determined to be leaked, the information recorded by this operation will be * provided to you via {@link ResourceLeakDetector}. * 記錄該物件的當前訪問位置,附加資訊用于除錯, * 如果確定該物件被泄露,將提供此操作記錄的資訊給您 */ ReferenceCounted touch(Object hint); /** * Decreases the reference count by {@code 1} and deallocates this object if the reference count reaches at * {@code 0}. * * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated * 參考計數減少,如果計數變為了0,則釋放物件資源 * 如果物件資源被釋放,則回傳true,否則回傳false */ boolean release(); /** * Decreases the reference count by the specified {@code decrement} and deallocates this object if the reference * count reaches at {@code 0}. * * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated * 參考計數-指定值,如果計數變為了0,則釋放物件資源或交回到物件池 * 如果物件資源被釋放,則回傳true,否則回傳false */ boolean release(int decrement); }
3、AbstractReferenceCountedByteBuf原始碼分析
AbstractReferenceCountedByteBuf對ReferenceCounted進行了具體實作,retain與release兩個方法通過CAS方式對參考計數refcnt進行操作,下面對其原始碼進行簡單分析
初始化
參考計數初始值refCnt 使用關鍵字volatile修飾,保證執行緒的可見性,同時使用偶數,參考增加通過位移操作實作,提高運算效率,
采用 AtomicIntegerFieldUpdater 物件,通過CAS方式更新refCnt,以實作執行緒安全,避免加鎖,提高效率,
private static final long REFCNT_FIELD_OFFSET; //采用 AtomicIntegerFieldUpdater 物件,CAS方式更新refCnt private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt"); //refCnt 實際值為偶數,采用位移操作提高效率 // even => "real" refcount is (refCnt >>> 1); odd => "real" refcount is 0 @SuppressWarnings("unused") private volatile int refCnt = 2;
retain操作
上面示例中每呼叫一次retain方法,參考計數就會累加一次,我們看下原始碼中retain的具體實作
public ByteBuf retain() { return retain0(1); } @Override public ByteBuf retain(int increment) { return retain0(checkPositive(increment, "increment")); } //計數器增值操作 private ByteBuf retain0(final int increment) { // all changes to the raw count are 2x the "real" change int adjustedIncrement = increment << 1; // overflow OK here 真正的計數都是2倍遞增 int oldRef = refCntUpdater.getAndAdd(this, adjustedIncrement); //通過CAS方式遞增并獲取原值 if ((oldRef & 1) != 0) {//判斷奇偶,正常情況這里應該都是偶數 throw new IllegalReferenceCountException(0, increment); } // don't pass 0! 如果計數小于等于0,以及整型范圍越界(0x7fffffff+1)拋出例外 if ((oldRef <= 0 && oldRef + adjustedIncrement >= 0) || (oldRef >= 0 && oldRef + adjustedIncrement < oldRef)) { // overflow case refCntUpdater.getAndAdd(this, -adjustedIncrement); throw new IllegalReferenceCountException(realRefCnt(oldRef), increment); } return this; }
release操作
通過呼叫release方法,對參考計數做減值操作,原始碼中release的具體實作要注意的是由于參考計數以2倍遞增,所以參考次數= 參考計數/2,當decrement=refcnt/2 也就是參考次數=釋放次數時,代表ByteBuf不再被參考,執行記憶體釋放或放回記憶體池的操作,
//計數器減值操作 private boolean release0(int decrement) { int rawCnt = nonVolatileRawCnt(), realCnt = toLiveRealCnt(rawCnt, decrement); //對計數器進行除以2操作,也就是參考次數 /** * /這里如注意 你傳入的減值引數decrement = realCnt 時 等同于 參考次數=釋放次數,直接進行釋放操作 */ if (decrement == realCnt) { if (refCntUpdater.compareAndSet(this, rawCnt, 1)) { //CAS方式置為1 deallocate();//記憶體釋放或放回記憶體池 return true; } return retryRelease0(decrement);//進入具體操作 } return releaseNonFinal0(decrement, rawCnt, realCnt); } private boolean releaseNonFinal0(int decrement, int rawCnt, int realCnt) { //如果decrement 小于 realCnt,通過CAS方式減去decrement*2 if (decrement < realCnt // all changes to the raw count are 2x the "real" change && refCntUpdater.compareAndSet(this, rawCnt, rawCnt - (decrement << 1))) { return false; } return retryRelease0(decrement); } private boolean retryRelease0(int decrement) { for (;;) { int rawCnt = refCntUpdater.get(this), realCnt = toLiveRealCnt(rawCnt, decrement); if (decrement == realCnt) { if (refCntUpdater.compareAndSet(this, rawCnt, 1)) { deallocate(); return true; } } else if (decrement < realCnt) {//如果decrement 小于 realCnt,通過CAS方式減去decrement*2 // all changes to the raw count are 2x the "real" change if (refCntUpdater.compareAndSet(this, rawCnt, rawCnt - (decrement << 1))) { return false; } } else { throw new IllegalReferenceCountException(realCnt, -decrement); } Thread.yield(); // this benefits throughput under high contention } } /** * Like {@link #realRefCnt(int)} but throws if refCnt == 0 */ private static int toLiveRealCnt(int rawCnt, int decrement) { if ((rawCnt & 1) == 0) { return rawCnt >>> 1; } // odd rawCnt => already deallocated throw new IllegalReferenceCountException(0, -decrement); }
4、總結
以上我們圍繞AbstractReferenceCountedByteBuf對Netty參考計數的具體實作進行了分析,可以看到Netty在實作參考計數的同時,結合CAS、位移計算等方式,保證了運算效率和執行緒安全,在實際專案中我們遇到類似應用場景也都可以借鑒參考,如資料發送次數,商品剩余數量等計數場景的實作,希望本文對大家能有所幫助,其中如有不足與不正確的地方還望指正與海涵,十分感謝,
關注微信公眾號,查看更多技術文章,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/398381.html
標籤:Java
