摘要:之所以稱它為卡車,只因編程思想中有段比喻我們可以把它想象成一個煤礦,通道是一個包含煤層數據的礦藏,而緩沖器則是派送到礦藏中的卡車。那么升級版卡車,自然指的就是。結構和功能之所以再次打造了升級版的緩沖器,顯然是不滿中的某些弊端。
卡車
卡車指的是java原生類ByteBuffer,這兄弟在NIO界大名鼎鼎,與Channel、Selector的鐵三角組合構筑了NIO的核心。之所以稱它為卡車,只因《編程思想》中有段比喻:
我們可以把它想象成一個煤礦,通道(Channel)是一個包含煤層(數據)的礦藏,而緩沖器(ByteBuffer)則是派送到礦藏中的卡車。卡車滿載煤炭而歸,我們再從卡車上獲得煤炭。也就是說,我們并沒有直接和通道交互;我們只是和緩沖器交互,并把緩沖器派送到通道。
那么升級版卡車,自然指的就是ByteBuf。
結構和功能Netty之所以再次打造了升級版的緩沖器,顯然是不滿ByteBuffer中的某些弊端。
ByteBuffer長度固定
使用者經常需要調用flip()、rewind()方法調整position的位置,不方便
API功能有限
ByteBuffer中有三個重要的位置屬性:position、limit、capacity,一個寫操作之后大概是這樣的
如若想進行讀操作,那么flip()的調用是少不了的,從圖中不難看出,目前position到limit啥也沒有。
調用flip()之后則不一樣了(我們不一樣~):
而ByteBuf的人設則不相同,它的兩個位置屬性readIndex、writeIndex,分別和讀操作、寫操作相對應。“寫”不操作readIndex,“讀”不操作writeIndex,兩者不會相互干擾。這里盜幾張圖說明下好了:
初始狀態
寫入N個字節
讀取M個(M 釋放已讀緩存discardReadBytes 重點在于ByteBuf的read和write相關方法,已經封裝好了對readIndex、writeIndex位置索引的操作,不需要使用者繁瑣的flip()。且write()方法中,ByteBuf設計了自動擴容,這一點后續章節會進行詳細說明。 功能方面,主要關注兩點:
Derived buffers,類似于數據庫視圖。ByteBuf提供了多個接口用于創建某ByteBuf的視圖或復制ByteBuf: duplicate:返回當前ByteBuf的復制對象,緩沖區內容共享(修改復制的ByteBuf,原來的ByteBuf內容也隨之改變),索引獨立維護。 copy:內容和索引都獨立。 slice:返回當前ByteBuf的可讀子緩沖區,內容共享,索引獨立。
轉換成ByteBuffer
nio的SocketChanel進行網絡操作,還是操作的java原生的ByteBuffer,所以ByteBuf轉換成ByteBuffer的需求還是有市場的。
ByteBuffer nioBuffer():當前ByteBuf的可讀緩沖區轉換成ByteBuffer,緩沖區內容共享,索引獨立。需要指出的是,返回后的ByteBuffer無法感知原ByteBuf的動態擴展操作。
ByteBuf星系稱之為“星系”,是因為ByteBuf一脈涉及到的類實在太多了,但多而不亂,歸功于類關系結構的設計。
類關系結構依然盜圖:
從內存分配角度,ByteBuf可分為兩類
堆內存HeapByteBuf字節緩沖區
直接內存DirectByteBuf字節緩沖區
從內存回收角度,ByteBuf也可分為兩類:
普通緩沖區UnpooledByteBuf
池化緩沖區PooledByteBuf
縱觀該關繼承節構,給我留下的印象就是每層各司其職:讀操作以及其它的一些公共功能由父類實現,差異化功能由子類實現。
下面聊下筆者感興趣的幾個點……
AbstractByteBuf的寫操作簇AbstractByteBuf的寫操作有很多,這里以writeBytes(byte[] src, int srcIndex, int length)方法為例
@Override public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { ensureWritable(length); //一、確保可寫,對邊界進行驗證 setBytes(writerIndex, src, srcIndex, length); //二、寫入操作,不同類型的子類實現方式不同 writerIndex += length; return this; }
注釋部分分別展開看下。
注釋一、確保可寫,對邊界進行驗證跟調用棧ensureWritable -> ensureWritable0,觀察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); //四、內存分配 }
比較
先對要寫入的字節數minWritableBytes進行判斷:如果minWritableBytes < capacity - writeIndex,那么很好,不需要擴容;如果minWritableBytes > maxCapacity - writerIndex,也就是要寫入字節數超過了允許的最大字節數,直接拋出越界異常IndexOutOfBoundsException。
眼尖的朋友可能發現了,兩次用來判斷的上界并不相同——capacity / maxCapacity。maxCapacity是AbstractByteBuf的屬性,而capacity設定在其子類中。簡單看下UnpooledDirectByteBuf的構造函數:
public UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(maxCapacity); //為AbstractByteBuf的maxCapacity屬性賦值 /** * …… * 省略無關部分 */ setByteBuffer(ByteBuffer.allocateDirect(initialCapacity)); //capacity賦值 }
也就是說,ByteBuf的結構,可看成這樣:
擴容計算
@Override public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { if (minNewCapacity < 0) { throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expected: 0+)"); } if (minNewCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "minNewCapacity: %d (expected: not greater than maxCapacity(%d)", minNewCapacity, maxCapacity)); } /** * 設置閥值為4MB * 1.如果擴展的容量大于閥值,對擴張后的內存和最大內存進行比較:大于最大長度使用最大長度,否則步進4M * 2.如果需要擴展的容量小于閥值,以64進行計數倍增:64->128->256;為防止倍增過猛,最后與最大值再次進行比較 */ final int threshold = CALCULATE_THRESHOLD; // 4 MiB page if (minNewCapacity == threshold) { return threshold; } // If over threshold, do not double but just increase by threshold. if (minNewCapacity > threshold) { int newCapacity = minNewCapacity / threshold * threshold; if (newCapacity > maxCapacity - threshold) { newCapacity = maxCapacity; } else { newCapacity += threshold; } return newCapacity; } // Not over threshold. Double up to 4 MiB, starting from 64. int newCapacity = 64; while (newCapacity < minNewCapacity) { newCapacity <<= 1; } return Math.min(newCapacity, maxCapacity); }
具體的擴容策略,已拍入注釋中,盡可查看!
注釋二、寫入操作,不同類型的子類實現方式不同對比下UnpooledDirectByteBuf和UnpooledHeapByteBuf的實現
UnpooledDirectByteBuf
@Override public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { checkSrcIndex(index, length, srcIndex, src.length); ByteBuffer tmpBuf = internalNioBuffer(); //分配 tmpBuf.clear().position(index).limit(index + length); tmpBuf.put(src, srcIndex, length); return this; }
UnpooledHeapByteBuf
@Override 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; }
篇幅有限,不展開說了,結論就是:
UnpooledDirectByteBuf的底層實現為ByteBuffer.allocateDirect,分配時復制體通過buffer.duplicate()獲取復制體;而UnpooledHeapByteBuf的底層實現為byte[],分配時通過System.arraycopy方法拷貝副本。
AbstractReferenceCountedByteBuf的名字就挺有意思——“引用計數”,一副JVM垃圾回收的即視感。而事實上,也差不多一個意思。
看下類屬性:
private static final AtomicIntegerFieldUpdaterrefCntUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt"); private volatile int refCnt;
以原子方式更新屬性的AtomicIntegerFieldUpdater起了關鍵作用,將會對volatile修飾的refCnt進行更新,見retain方法(下面展示的是retain的關鍵部分retain0):
private ByteBuf retain0(final int increment) { int oldRef = refCntUpdater.getAndAdd(this, increment); if (oldRef <= 0 || oldRef + increment < oldRef) { // Ensure we don"t resurrect (which means the refCnt was 0) and also that we encountered an overflow. refCntUpdater.getAndAdd(this, -increment); throw new IllegalReferenceCountException(oldRef, increment); } return this; }
源碼閱讀很有意思的一點就是能看到些自己不熟悉的類,比如AtomicIntegerFieldUpdater我以前就沒接觸過!
內存池內存池可有效的提升效率,道理和線程池、數據庫連接池相通,即省去了重復創建銷毀的過程。
到目前為止,看到的都是ByteBuf中的各Unpooled實現,而池化版的ByteBuf沒怎么提過。為何如此?因為池化的實現較復雜,以我現在的功力尚不能完全掌握透徹。
先聊下內存池的設計思路,漲漲姿勢:
為了集中集中管理內存的分配和釋放,同事提高分配和釋放內存時候的性能,很多框架和應用都會通過預先申請一大塊內存,然后通過提供相應的分配和釋放接口來使用內存。這樣一來,堆內存的管理就被集中到幾個類或函數中,由于不再頻繁使用系統調用來申請和釋放內存,應用或系統的性能也會大大提高。 ——節選自《Netty權威指南》
Netty的ByteBuf內存池也是按照這個思路搞的。首先,看下官方注釋:
/** * Notation: The following terms are important to understand the code * > page - a page is the smallest unit of memory chunk that can be allocated * > chunk - a chunk is a collection of pages * > in this code chunkSize = 2^{maxOrder} * pageSize */
這里面有兩個重要的概念page(頁)和chunk(塊),chunk管理多個page組成二叉樹結構,大概就是這個樣子:
選擇二叉樹是有原因的:
/** * To search for the first offset in chunk that has at least requested size available we construct a * complete balanced binary tree and store it in an array (just like heaps) - memoryMap */
為了在chunk中找到至少可用的size的偏移量offset。
繼線性結構后,人們又發明了樹形結構的意義在于“提升查詢效率”,也同樣是這里選擇二叉樹的原因。
小于一個page的內存,直接在PoolSubpage中分配完成。
某塊內存是否分配,將通過狀態位進行標識。
后記一如既往的啰嗦幾句,最近工作忙,更新文章較慢,希望自己能堅持,如發現問題望大家指正!
thanks..
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68669.html
摘要:搞懂了這部分后,我們將明白在世界中扮演的角色進擊的此圖展示的已經算是優化后的了用到了線程池。多線程將這種處理操作分隔出來,非型操作業務操作配備以線程池,進化成多線程模型這樣的架構,系統瓶頸轉移至部分。 Channel定位 注意:如無特別說明,文中的Channel都指的是Netty Channel(io.netty.channel) 一周時間的Channel家族學習,一度讓我懷疑人生——...
摘要:目前為止,我們已經完成了一半的工作,剩下的就是在方法中啟動服務器。第一個通常被稱為,負責接收已到達的。這兩個指針恰好標記著數據的起始終止位置。 前言 本篇翻譯自netty官方Get Start教程,一方面能把好的文章分享給各位,另一方面能鞏固所學的知識。若有錯誤和遺漏,歡迎各位指出。 https://netty.io/wiki/user-gu... 面臨的問題 我們一般使用專用軟件或者...
摘要:轉發自 轉發自 http://netty.io/wiki/referenc... Since Netty version 4, the life cycle of certain objects are managed by their reference counts, so that Netty can return them (or their shared resources)...
摘要:根據對的定義即所謂的就是在操作數據時不需要將數據從一個內存區域拷貝到另一個內存區域因為少了一次內存的拷貝因此的效率就得到的提升在層面上的通常指避免在用戶態與內核態之間來回拷貝數據例如提供的系統調用它可以將一段用戶空間內存映射到內 根據 Wiki 對 Zero-copy 的定義: Zero-copy describes computer operations in which the C...
摘要:提供了作為它的字節容器但是這個類使用起來過于復雜而且也有些繁瑣的的代替品是的的數據處理通過兩個組件暴露下面是的優點它可以被用戶自定義的緩沖區類擴展通過內置的復合緩沖區類型實現了透明的零拷貝容量可以按需增長在讀和寫這兩種模式之間雀環不需要調用 Java NIO 提供了 ByteBuffer 作為它的字節容器, 但是這個類使用起來過于復雜, 而且也有些繁瑣. Netty 的 ByteBuf...
閱讀 3133·2021-11-19 09:40
閱讀 2430·2021-10-14 09:42
閱讀 1698·2021-09-22 15:34
閱讀 1441·2019-08-30 15:55
閱讀 775·2019-08-29 12:59
閱讀 412·2019-08-28 18:28
閱讀 1818·2019-08-26 13:42
閱讀 1519·2019-08-26 13:29