摘要:之所以稱它為卡車,只因編程思想中有段比喻我們可以把它想象成一個(gè)煤礦,通道是一個(gè)包含煤層數(shù)據(jù)的礦藏,而緩沖器則是派送到礦藏中的卡車。那么升級(jí)版卡車,自然指的就是。結(jié)構(gòu)和功能之所以再次打造了升級(jí)版的緩沖器,顯然是不滿中的某些弊端。
卡車
卡車指的是java原生類ByteBuffer,這兄弟在NIO界大名鼎鼎,與Channel、Selector的鐵三角組合構(gòu)筑了NIO的核心。之所以稱它為卡車,只因《編程思想》中有段比喻:
我們可以把它想象成一個(gè)煤礦,通道(Channel)是一個(gè)包含煤層(數(shù)據(jù))的礦藏,而緩沖器(ByteBuffer)則是派送到礦藏中的卡車。卡車滿載煤炭而歸,我們?cè)購目ㄜ嚿汐@得煤炭。也就是說,我們并沒有直接和通道交互;我們只是和緩沖器交互,并把緩沖器派送到通道。
那么升級(jí)版卡車,自然指的就是ByteBuf。
結(jié)構(gòu)和功能Netty之所以再次打造了升級(jí)版的緩沖器,顯然是不滿ByteBuffer中的某些弊端。
ByteBuffer長度固定
使用者經(jīng)常需要調(diào)用flip()、rewind()方法調(diào)整position的位置,不方便
API功能有限
ByteBuffer中有三個(gè)重要的位置屬性:position、limit、capacity,一個(gè)寫操作之后大概是這樣的
如若想進(jìn)行讀操作,那么flip()的調(diào)用是少不了的,從圖中不難看出,目前position到limit啥也沒有。
調(diào)用flip()之后則不一樣了(我們不一樣~):
而ByteBuf的人設(shè)則不相同,它的兩個(gè)位置屬性readIndex、writeIndex,分別和讀操作、寫操作相對(duì)應(yīng)。“寫”不操作readIndex,“讀”不操作writeIndex,兩者不會(huì)相互干擾。這里盜幾張圖說明下好了:
初始狀態(tài)
寫入N個(gè)字節(jié)
讀取M個(gè)(M 釋放已讀緩存discardReadBytes 重點(diǎn)在于ByteBuf的read和write相關(guān)方法,已經(jīng)封裝好了對(duì)readIndex、writeIndex位置索引的操作,不需要使用者繁瑣的flip()。且write()方法中,ByteBuf設(shè)計(jì)了自動(dòng)擴(kuò)容,這一點(diǎn)后續(xù)章節(jié)會(huì)進(jìn)行詳細(xì)說明。 功能方面,主要關(guān)注兩點(diǎn):
Derived buffers,類似于數(shù)據(jù)庫視圖。ByteBuf提供了多個(gè)接口用于創(chuàng)建某ByteBuf的視圖或復(fù)制ByteBuf: duplicate:返回當(dāng)前ByteBuf的復(fù)制對(duì)象,緩沖區(qū)內(nèi)容共享(修改復(fù)制的ByteBuf,原來的ByteBuf內(nèi)容也隨之改變),索引獨(dú)立維護(hù)。 copy:內(nèi)容和索引都獨(dú)立。 slice:返回當(dāng)前ByteBuf的可讀子緩沖區(qū),內(nèi)容共享,索引獨(dú)立。
轉(zhuǎn)換成ByteBuffer
nio的SocketChanel進(jìn)行網(wǎng)絡(luò)操作,還是操作的java原生的ByteBuffer,所以ByteBuf轉(zhuǎn)換成ByteBuffer的需求還是有市場(chǎng)的。
ByteBuffer nioBuffer():當(dāng)前ByteBuf的可讀緩沖區(qū)轉(zhuǎn)換成ByteBuffer,緩沖區(qū)內(nèi)容共享,索引獨(dú)立。需要指出的是,返回后的ByteBuffer無法感知原ByteBuf的動(dòng)態(tài)擴(kuò)展操作。
ByteBuf星系稱之為“星系”,是因?yàn)锽yteBuf一脈涉及到的類實(shí)在太多了,但多而不亂,歸功于類關(guān)系結(jié)構(gòu)的設(shè)計(jì)。
類關(guān)系結(jié)構(gòu)依然盜圖:
從內(nèi)存分配角度,ByteBuf可分為兩類
堆內(nèi)存HeapByteBuf字節(jié)緩沖區(qū)
直接內(nèi)存DirectByteBuf字節(jié)緩沖區(qū)
從內(nèi)存回收角度,ByteBuf也可分為兩類:
普通緩沖區(qū)UnpooledByteBuf
池化緩沖區(qū)PooledByteBuf
縱觀該關(guān)繼承節(jié)構(gòu),給我留下的印象就是每層各司其職:讀操作以及其它的一些公共功能由父類實(shí)現(xiàn),差異化功能由子類實(shí)現(xiàn)。
下面聊下筆者感興趣的幾個(gè)點(diǎn)……
AbstractByteBuf的寫操作簇AbstractByteBuf的寫操作有很多,這里以writeBytes(byte[] src, int srcIndex, int length)方法為例
@Override public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { ensureWritable(length); //一、確??蓪懀瑢?duì)邊界進(jìn)行驗(yàn)證 setBytes(writerIndex, src, srcIndex, length); //二、寫入操作,不同類型的子類實(shí)現(xiàn)方式不同 writerIndex += length; return this; }
注釋部分分別展開看下。
注釋一、確??蓪懀瑢?duì)邊界進(jìn)行驗(yàn)證跟調(diào)用棧ensureWritable -> ensureWritable0,觀察ensureWritable0方法
final void ensureWritable0(int minWritableBytes) { ensureAccessible(); //確保對(duì)象可用 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. // 三、計(jì)算擴(kuò)容量 int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity); // Adjust to the new capacity. capacity(newCapacity); //四、內(nèi)存分配 }
比較
先對(duì)要寫入的字節(jié)數(shù)minWritableBytes進(jìn)行判斷:如果minWritableBytes < capacity - writeIndex,那么很好,不需要擴(kuò)容;如果minWritableBytes > maxCapacity - writerIndex,也就是要寫入字節(jié)數(shù)超過了允許的最大字節(jié)數(shù),直接拋出越界異常IndexOutOfBoundsException。
眼尖的朋友可能發(fā)現(xiàn)了,兩次用來判斷的上界并不相同——capacity / maxCapacity。maxCapacity是AbstractByteBuf的屬性,而capacity設(shè)定在其子類中。簡單看下UnpooledDirectByteBuf的構(gòu)造函數(shù):
public UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(maxCapacity); //為AbstractByteBuf的maxCapacity屬性賦值 /** * …… * 省略無關(guān)部分 */ setByteBuffer(ByteBuffer.allocateDirect(initialCapacity)); //capacity賦值 }
也就是說,ByteBuf的結(jié)構(gòu),可看成這樣:
擴(kuò)容計(jì)算
@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)); } /** * 設(shè)置閥值為4MB * 1.如果擴(kuò)展的容量大于閥值,對(duì)擴(kuò)張后的內(nèi)存和最大內(nèi)存進(jìn)行比較:大于最大長度使用最大長度,否則步進(jìn)4M * 2.如果需要擴(kuò)展的容量小于閥值,以64進(jìn)行計(jì)數(shù)倍增:64->128->256;為防止倍增過猛,最后與最大值再次進(jìn)行比較 */ 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); }
具體的擴(kuò)容策略,已拍入注釋中,盡可查看!
注釋二、寫入操作,不同類型的子類實(shí)現(xiàn)方式不同對(duì)比下UnpooledDirectByteBuf和UnpooledHeapByteBuf的實(shí)現(xiàn)
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; }
篇幅有限,不展開說了,結(jié)論就是:
UnpooledDirectByteBuf的底層實(shí)現(xiàn)為ByteBuffer.allocateDirect,分配時(shí)復(fù)制體通過buffer.duplicate()獲取復(fù)制體;而UnpooledHeapByteBuf的底層實(shí)現(xiàn)為byte[],分配時(shí)通過System.arraycopy方法拷貝副本。
AbstractReferenceCountedByteBuf的名字就挺有意思——“引用計(jì)數(shù)”,一副JVM垃圾回收的即視感。而事實(shí)上,也差不多一個(gè)意思。
看下類屬性:
private static final AtomicIntegerFieldUpdaterrefCntUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt"); private volatile int refCnt;
以原子方式更新屬性的AtomicIntegerFieldUpdater起了關(guān)鍵作用,將會(huì)對(duì)volatile修飾的refCnt進(jìn)行更新,見retain方法(下面展示的是retain的關(guān)鍵部分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; }
源碼閱讀很有意思的一點(diǎn)就是能看到些自己不熟悉的類,比如AtomicIntegerFieldUpdater我以前就沒接觸過!
內(nèi)存池內(nèi)存池可有效的提升效率,道理和線程池、數(shù)據(jù)庫連接池相通,即省去了重復(fù)創(chuàng)建銷毀的過程。
到目前為止,看到的都是ByteBuf中的各Unpooled實(shí)現(xiàn),而池化版的ByteBuf沒怎么提過。為何如此?因?yàn)槌鼗膶?shí)現(xiàn)較復(fù)雜,以我現(xiàn)在的功力尚不能完全掌握透徹。
先聊下內(nèi)存池的設(shè)計(jì)思路,漲漲姿勢(shì):
為了集中集中管理內(nèi)存的分配和釋放,同事提高分配和釋放內(nèi)存時(shí)候的性能,很多框架和應(yīng)用都會(huì)通過預(yù)先申請(qǐng)一大塊內(nèi)存,然后通過提供相應(yīng)的分配和釋放接口來使用內(nèi)存。這樣一來,堆內(nèi)存的管理就被集中到幾個(gè)類或函數(shù)中,由于不再頻繁使用系統(tǒng)調(diào)用來申請(qǐng)和釋放內(nèi)存,應(yīng)用或系統(tǒng)的性能也會(huì)大大提高。 ——節(jié)選自《Netty權(quán)威指南》
Netty的ByteBuf內(nèi)存池也是按照這個(gè)思路搞的。首先,看下官方注釋:
/** * 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 */
這里面有兩個(gè)重要的概念page(頁)和chunk(塊),chunk管理多個(gè)page組成二叉樹結(jié)構(gòu),大概就是這個(gè)樣子:
選擇二叉樹是有原因的:
/** * 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。
繼線性結(jié)構(gòu)后,人們又發(fā)明了樹形結(jié)構(gòu)的意義在于“提升查詢效率”,也同樣是這里選擇二叉樹的原因。
小于一個(gè)page的內(nèi)存,直接在PoolSubpage中分配完成。
某塊內(nèi)存是否分配,將通過狀態(tài)位進(jìn)行標(biāo)識(shí)。
后記一如既往的啰嗦幾句,最近工作忙,更新文章較慢,希望自己能堅(jiān)持,如發(fā)現(xiàn)問題望大家指正!
thanks..
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/68669.html
摘要:搞懂了這部分后,我們將明白在世界中扮演的角色進(jìn)擊的此圖展示的已經(jīng)算是優(yōu)化后的了用到了線程池。多線程將這種處理操作分隔出來,非型操作業(yè)務(wù)操作配備以線程池,進(jìn)化成多線程模型這樣的架構(gòu),系統(tǒng)瓶頸轉(zhuǎn)移至部分。 Channel定位 注意:如無特別說明,文中的Channel都指的是Netty Channel(io.netty.channel) 一周時(shí)間的Channel家族學(xué)習(xí),一度讓我懷疑人生——...
摘要:目前為止,我們已經(jīng)完成了一半的工作,剩下的就是在方法中啟動(dòng)服務(wù)器。第一個(gè)通常被稱為,負(fù)責(zé)接收已到達(dá)的。這兩個(gè)指針恰好標(biāo)記著數(shù)據(jù)的起始終止位置。 前言 本篇翻譯自netty官方Get Start教程,一方面能把好的文章分享給各位,另一方面能鞏固所學(xué)的知識(shí)。若有錯(cuò)誤和遺漏,歡迎各位指出。 https://netty.io/wiki/user-gu... 面臨的問題 我們一般使用專用軟件或者...
摘要:轉(zhuǎn)發(fā)自 轉(zhuǎn)發(fā)自 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)...
摘要:根據(jù)對(duì)的定義即所謂的就是在操作數(shù)據(jù)時(shí)不需要將數(shù)據(jù)從一個(gè)內(nèi)存區(qū)域拷貝到另一個(gè)內(nèi)存區(qū)域因?yàn)樯倭艘淮蝺?nèi)存的拷貝因此的效率就得到的提升在層面上的通常指避免在用戶態(tài)與內(nèi)核態(tài)之間來回拷貝數(shù)據(jù)例如提供的系統(tǒng)調(diào)用它可以將一段用戶空間內(nèi)存映射到內(nèi) 根據(jù) Wiki 對(duì) Zero-copy 的定義: Zero-copy describes computer operations in which the C...
摘要:提供了作為它的字節(jié)容器但是這個(gè)類使用起來過于復(fù)雜而且也有些繁瑣的的代替品是的的數(shù)據(jù)處理通過兩個(gè)組件暴露下面是的優(yōu)點(diǎn)它可以被用戶自定義的緩沖區(qū)類擴(kuò)展通過內(nèi)置的復(fù)合緩沖區(qū)類型實(shí)現(xiàn)了透明的零拷貝容量可以按需增長在讀和寫這兩種模式之間雀環(huán)不需要調(diào)用 Java NIO 提供了 ByteBuffer 作為它的字節(jié)容器, 但是這個(gè)類使用起來過于復(fù)雜, 而且也有些繁瑣. Netty 的 ByteBuf...
閱讀 3210·2021-11-19 09:40
閱讀 2524·2021-10-14 09:42
閱讀 1812·2021-09-22 15:34
閱讀 1514·2019-08-30 15:55
閱讀 858·2019-08-29 12:59
閱讀 472·2019-08-28 18:28
閱讀 1881·2019-08-26 13:42
閱讀 1593·2019-08-26 13:29