netty中的ByteBuf源碼詳解
一、背景簡(jiǎn)介
ByteBuf,顧名思義,就是字節(jié)緩沖區(qū),是Netty中非常重要的一個(gè)組件。熟悉jdk NIO的同學(xué)應(yīng)該知道ByteBuffer,正是因?yàn)閖dk原生ByteBuffer使用比較復(fù)雜,某些場(chǎng)景下性能不是太好,netty開發(fā)團(tuán)隊(duì)重新設(shè)計(jì)了ByteBuf用以替代原生ByteBuffer。
二、ByteBuf和ByteBuffer對(duì)比
下面用圖示來展示ByteBuf和ByteBuffer工作原理:
ByteBuffer
ByteBuffer依靠flip()來切換模式,在讀模式下調(diào)用flip()切換為寫模式,在寫模式下limit和capacity相等,position標(biāo)識(shí)當(dāng)前寫的位置。在寫模式下調(diào)用flip()切換為讀模式,在讀模式下position回到起始位置開始讀,limit回到position位置表示能讀到多少數(shù)據(jù),capacity不變表示緩存區(qū)容量大小。
capacity:在讀/寫模式下都是固定的,就是緩沖區(qū)容量大小。
position:讀/寫位置指針,表示當(dāng)前讀(寫)到什么位置。
limit:在寫模式下表示最多能寫入多少數(shù)據(jù),此時(shí)和capacity相同。在讀模式下表示最多能讀多少數(shù)據(jù),此時(shí)它的值等于緩存區(qū)中實(shí)際數(shù)據(jù)量的大小。
ByteBuf
ByteBuf主要是通過readerIndex和writerIndex兩個(gè)指針進(jìn)行數(shù)據(jù)的讀和寫,整個(gè)ByteBuf被這兩個(gè)指針最多分成三個(gè)部分,分別是可丟棄部分,可讀部分和可寫部分
剛初始化的時(shí)候,整個(gè)緩沖區(qū)還沒有數(shù)據(jù),讀寫指針都指向0,所有的內(nèi)容都是可寫部分,此時(shí)還沒有可讀部分和可丟棄部分,如下:
當(dāng)寫完N個(gè)字節(jié)數(shù)據(jù)后,讀指針仍然是0,因?yàn)檫€沒有開始進(jìn)行讀事件,寫指針向后移動(dòng)了N個(gè)字節(jié)的位置,如下:
當(dāng)開始讀數(shù)據(jù)并且讀取M個(gè)字節(jié)數(shù)據(jù)之后(M<N)寫指針位置不變,讀指針后移動(dòng)了M個(gè)字節(jié)的位置,如下:
當(dāng)可丟棄部分?jǐn)?shù)據(jù)被清空之后,readerindex重新回到起始位置,writerindex的位置為writerindex的值減去之前的readerindex,也就是M,相關(guān)圖示如下:
調(diào)用clear之后,writerindex和readerinde全部復(fù)位為0。它不會(huì)清除緩沖區(qū)內(nèi)容(例如,用填充0),而只是清除兩個(gè)指針。
更改的讀寫指針的值,每個(gè)位置上原本的字節(jié)內(nèi)容并沒有發(fā)生改變,只是變成了可寫狀態(tài)而已。
另請(qǐng)注意,此操作的語義不同于Buffer.clear()。
三、源碼
明白了ByteBuf工作原理之后,ByteBuf相關(guān)的api就很好理解了,在此附上netty官方api文檔,以供參閱
我們?cè)谶@里看下netty擴(kuò)容相關(guān)源碼邏輯。
擴(kuò)容肯定是在寫入數(shù)據(jù)的時(shí)候會(huì)由相關(guān)邏輯判斷,我們隨便進(jìn)入一個(gè)寫入字節(jié)的api方法。
public abstract ByteBuf writeBytes(byte[] src);
進(jìn)入到其抽象子類AbstractByteBuf中。
@Override public ByteBuf writeBytes(byte[] src) { writeBytes(src, 0, src.length); return this; } @Override public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { ensureAccessible(); ensureWritable(length); setBytes(writerIndex, src, srcIndex, length); writerIndex += length; return this; }
首先ensureAccessible進(jìn)行安全校驗(yàn),每種嘗試訪問緩沖區(qū)內(nèi)容的方法都應(yīng)調(diào)用此方法,以檢查緩沖區(qū)是否已釋放。然后ensureWritable判斷是否可寫,擴(kuò)容相關(guān)邏輯就在這里進(jìn)行判斷,如果緩沖區(qū)可寫執(zhí)行setBytes進(jìn)行數(shù)據(jù)寫入,然后writerindex向后移動(dòng)length的位置,最后將ByteBuf對(duì)象進(jìn)行返回。我們重點(diǎn)看ensureWritable。
@Override public ByteBuf ensureWritable(int minWritableBytes) { if (minWritableBytes < 0) { throw new IllegalArgumentException(String.format( "minWritableBytes: %d (expected: >= 0)", minWritableBytes)); } ensureWritable0(minWritableBytes); return this; }
直接進(jìn)入ensureWritable0(minWritableBytes)方法中,此時(shí)minWritableBytes就是我們計(jì)劃需要申請(qǐng)的內(nèi)存大小空間。
private void ensureWritable0(int minWritableBytes) { // 安全檢查,保證寫入之前是可訪問的 //ensureAccessible(); ? // 可寫,不必?cái)U(kuò)容 if (minWritableBytes <= writableBytes()) { return; } ? //下標(biāo)越界 if (minWritableBytes > maxCapacity - writerIndex) { throw new IndexOutOfBoundsException(String.format( "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", writerIndex, minWritableBytes, maxCapacity, this)); } ? //達(dá)到臨界條件,開始執(zhí)行擴(kuò)容邏輯 // 計(jì)算新的容量,實(shí)際上為當(dāng)前容量擴(kuò)容至2的冪次方大小(具體是多少需要進(jìn)行后續(xù)判斷和計(jì)算) int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity); // 擴(kuò)容后的容量 capacity(newCapacity); }
可以看到真正開辟內(nèi)存空間新容量邏輯處理的是 alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity)執(zhí)行的,進(jìn)入到方法里面。
來到其實(shí)現(xiàn)類AbstractByteBufAllocator的calculateNewCapacity方法。
@Override public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { if (minNewCapacity < 0) { throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expectd: 0+)"); } if (minNewCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "minNewCapacity: %d (expected: not greater than maxCapacity(%d)", minNewCapacity, maxCapacity)); } // 擴(kuò)容的閾值,4兆字節(jié)大小 final int threshold = 1048576 * 4; if (minNewCapacity == threshold) { return threshold; } //如果計(jì)劃一共需要的內(nèi)存容量大小大于閾值,則需要和最大容量j進(jìn)行比較 if (minNewCapacity > threshold) { int newCapacity = minNewCapacity / threshold * threshold; if (newCapacity + threshold > maxCapacity) { newCapacity = maxCapacity; } else { newCapacity += threshold; } return newCapacity; } ? //如果計(jì)劃一共需要的內(nèi)存容量大小小于閾值,則以64為基數(shù)進(jìn)行倍增 int newCapacity = 64; while (newCapacity < minNewCapacity) { newCapacity <<= 1; } return Math.min(newCapacity, maxCapacity); }
minNewCapacity是我們計(jì)劃一共需要的內(nèi)存容量大小,maxCapacity是最大緩沖區(qū)容量大小。首先判斷minNewCapacity 是否小于零或者minNewCapacity 是否大于maxCapacity,滿足任一都拋出異常信息,然后判斷我們計(jì)劃一共需要的內(nèi)存容量大小minNewCapacity 是否等于了閾值4M:
①、如果等于了閾值,新容量大小就是閾值4M。
②、如果計(jì)劃一共需要的內(nèi)存容量大小大于閾值,則maxCapacity和minNewCapacity 相對(duì)于閾值的整數(shù)倍再加上一個(gè)閾值進(jìn)行大小判斷,如果大于maxCapacity,則新容量最大就是maxCapacity,返回maxCapacity,如果小于maxCapacity,則相當(dāng)于按照閾值的2倍進(jìn)行擴(kuò)容。
③、如果計(jì)劃一共需要的內(nèi)存容量大小小于閾值,則以64為基數(shù)只要小于我們計(jì)劃需要的內(nèi)存容量大小,就2倍擴(kuò)容,最后選取循環(huán)后的擴(kuò)容值和最大值兩個(gè)值其中的較小者。
至此擴(kuò)容就完成了,總結(jié)來說就是在擴(kuò)容過程中有一個(gè)擴(kuò)容需要容量的一個(gè)閾值4M,如果我們需要的內(nèi)存空間等于這個(gè)閾值,那么擴(kuò)容后的容量就是閾值大小,如果我們需要的內(nèi)存容量大小大于閾值或者小于閾值,其擴(kuò)容邏輯判斷和擴(kuò)容后返回的容量大小是不同的。但是最終擴(kuò)容后的容量大小總是2的冪次方大小并且不會(huì)比maxCapacity大。
四、ByteBuf主要的繼承關(guān)系
從內(nèi)存分配的角度看,ByteBuf可以分為兩類
(1)堆內(nèi)存(HeapByteBuf)字節(jié)緩沖區(qū):特點(diǎn)是內(nèi)存的分配和回收速度快,可以被JVM自動(dòng)收回;缺點(diǎn)就是如果進(jìn)行Socket的I/O讀寫,需要額外做一次內(nèi)存復(fù)制,將堆內(nèi)存對(duì)應(yīng)的緩沖區(qū)復(fù)制到內(nèi)核Chanenel中,性能會(huì)有一定程度的下降。
(2)直接內(nèi)存(DirectByteBuf) 字節(jié)緩沖區(qū):非堆內(nèi)存,它在堆外進(jìn)行內(nèi)存分配,相比于堆內(nèi)存,它的分配和回收速度會(huì)慢一些,但是將它寫入或者從Socket Channel中讀取時(shí),由于少了一次內(nèi)存復(fù)制,速度比堆內(nèi)存快。
正式因?yàn)楦饔欣?,所以Netty提供了多種ByteBuf供開發(fā)者使用,經(jīng)驗(yàn)表明,ByteBuf的最佳實(shí)踐是在I/O通信線程的讀寫緩沖區(qū)使用DirectByteBuf,后端業(yè)務(wù)消息的編解碼模塊使用HeapByteBuf,這樣組合可以達(dá)到性能最優(yōu)。
從內(nèi)存回收角度看,ByteBuf也可以分為兩類:基于對(duì)象池的ByteBuf和普通ByteBuf。兩者的主要區(qū)別就是基于對(duì)象池的ByteBuf可以重用ByteBuf對(duì)象,它自己維護(hù)了一個(gè)內(nèi)存池,可以循環(huán)利用創(chuàng)建的ByteBuf,提升內(nèi)存的使用效率,降低由于高負(fù)載導(dǎo)致的頻繁GC。測(cè)試表名使用內(nèi)存池后的Netty在高負(fù)載、大并發(fā)的沖擊下內(nèi)存和GC更加平穩(wěn)。
到此這篇關(guān)于netty中的ByteBuf源碼詳解的文章就介紹到這了,更多相關(guān)netty中的ByteBuf內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
try-cache-finally讀取文件錯(cuò)誤try-with-resources使用方法
這篇文章主要為大家介紹了try-cache-finally讀取文件錯(cuò)誤try-with-resources使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02SpringMVC中請(qǐng)求參數(shù)的獲取方式
這篇文章主要為大家介紹了SpringMVC中請(qǐng)求參數(shù)的獲取方式,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05SpringBoot簡(jiǎn)單實(shí)現(xiàn)文件上傳
這篇文章主要介紹了SpringBoot簡(jiǎn)單實(shí)現(xiàn)文件上傳,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-09-09springboot?vue接口測(cè)試HutoolUtil?TreeUtil處理樹形結(jié)構(gòu)
這篇文章主要介紹了springboot?vue接口測(cè)試HutoolUtil?TreeUtil處理樹形結(jié)構(gòu),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05java快速排序和選擇排序?qū)崿F(xiàn)實(shí)例解析
這篇文章主要為大家介紹了java快速排序和選擇排序?qū)崿F(xiàn)實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11