JDK又在寫B(tài)ug!告訴你為何Java NIO的ByteBuffer這么垃圾!

      網(wǎng)友投稿 789 2025-04-02

      網(wǎng)絡(luò)數(shù)據(jù)的基本單位永遠(yuǎn)是 byte(字節(jié))。Java NIO 提供 ByteBuffer 作為字節(jié)的容器,但該類過于復(fù)雜,有點難用。

      ByteBuf是Netty當(dāng)中的最重要的工具類,它與JDK的ByteBuffer原理基本上相同,也分為堆內(nèi)與堆外倆種類型,但是ByteBuf做了極大的優(yōu)化,具有更簡單的API,更多的工具方法和優(yōu)秀的內(nèi)存池設(shè)計。

      1 API

      Netty 的數(shù)據(jù)處理 API 通過兩個組件暴露——抽象類ByteBuf 和 接口 ByteBufHolder。

      ByteBuf API 的優(yōu)點:

      它可以被用戶自定義的緩沖區(qū)類型擴(kuò)展

      通過內(nèi)置的復(fù)合緩沖區(qū)類型實現(xiàn)了透明的零拷貝;

      容量可以按需增長(類似于 JDK 的 StringBuilder)

      在讀和寫這兩種模式之間切換不需要調(diào)用 ByteBuffer 的 flip()方法

      讀和寫使用了不同的索引

      支持方法的鏈?zhǔn)秸{(diào)用

      支持引用計數(shù)

      支持池化

      其他類可用于管理 ByteBuf 實例的分配,以及執(zhí)行各種針對于數(shù)據(jù)容器本身和它所持有的數(shù)據(jù)的操作。

      2 Netty 的數(shù)據(jù)容器

      所有網(wǎng)絡(luò)通信最終都是基于底層的字節(jié)流傳輸,因此高效、方便、易用的數(shù)據(jù)接口是迷人的,而 Netty 的 ByteBuf 生而為滿足這些需求。

      2.1 工作原理

      ByteBuf 維護(hù)倆不同索引:一個用于讀取,一個用于寫入:

      從 ByteBuf 讀取時,其 readerIndex 將會被遞增已經(jīng)被讀取的字節(jié)數(shù)

      當(dāng)寫入 ByteBuf 時,writerIndex 也會被遞增

      一個讀索引和寫索引都設(shè)置為 0 的 16 字節(jié) ByteBuf

      這些索引兩兩之間有什么關(guān)系呢?

      若打算讀取字節(jié)直到 readerIndex == writerIndex,會發(fā)生啥?此時,將會到達(dá)“可讀取的”數(shù)據(jù)的末尾。類似試圖讀取超出數(shù)組末尾的數(shù)據(jù)一樣,試圖讀取超出該點的數(shù)據(jù)也會拋 IndexOutOfBoundsException。

      read、write 開頭的 ByteBuf 方法,會推進(jìn)對應(yīng)索引

      set、get 開頭的操作則不會。后面的這些方法將在作為一個參數(shù)傳入的一個相對索引上執(zhí)行操作

      可指定 ByteBuf 的最大容量。試圖移動寫索引(即 writerIndex)超過這個值將會觸

      發(fā)一個異常。(默認(rèn)限制 Integer.MAX_VALUE。)

      內(nèi)存池化

      非池化的堆內(nèi)與堆外的 ByteBuf 示意圖

      ByteBuf heapBuffer = UnpooledByteBufAllocator.DEFAULT.heapBuffer(10); ByteBuf directBuffer = UnpooledByteBufAllocator.DEFAULT.directBuffer(10);

      1

      2

      注意要手動將GC 無法控制的非堆內(nèi)存的空間釋放:

      池化的堆內(nèi)與堆外的 ByteBuf 示意圖

      字節(jié)級操作

      派生緩沖區(qū)

      派生緩沖區(qū)為 ByteBuf 提供了以專門的方式來呈現(xiàn)其內(nèi)容的視圖。這類視圖通過以下方法創(chuàng)建:

      Unpooled.unmodifiableBuffer(…)

      order(ByteOrder)

      readSlice(int)

      這些方法都將返回一個新的 ByteBuf 實例,但都具有自己獨(dú)立的讀、寫和標(biāo)記索引。

      其內(nèi)部存儲和 JDK 的 ByteBuffer 一樣,都是共享的。所以派生緩沖區(qū)的創(chuàng)建成本很低,但同時也表明若你修改了它的內(nèi)容,也會同時修改對應(yīng)源實例!

      slice、slice(int, int)、retainedSlice、retainedSlice(int, int)

      返回此緩沖區(qū)的可讀字節(jié)的一部分。

      此方法與buf.slice(buf.readerIndex(), buf.readableBytes())相同。

      該方法不會調(diào)用retain(),引用計數(shù)不會增加。

      retainedSlice系列方法調(diào)用類似slice().retain(),但此方法可能返回產(chǎn)生較少垃圾的緩沖區(qū)實現(xiàn)。

      duplicate、retainedDuplicate

      返回一個共享該緩沖區(qū)整個區(qū)域的緩沖區(qū)。

      此方法不會修改此緩沖區(qū)的readerIndex或writerIndex

      讀取器和寫入器標(biāo)記將不會重復(fù)。

      duplicate不會調(diào)用retain(),不會增加引用計數(shù),而retainedDuplicate會。

      readSlice、readRetainedSlice

      返回部分空間,彼此共享底層緩沖區(qū),會增加原緩沖區(qū)的readerIndex。

      如果需要一個現(xiàn)有緩沖區(qū)的真實副本,請使用 copy()或者 copy(int, int),因為這個調(diào)用所返回的 ByteBuf 擁有獨(dú)立的數(shù)據(jù)副本。

      引用與釋放

      ByteBuf 在使用完畢后一定要記得釋放,否則會造成內(nèi)存泄露。

      引用計數(shù)

      JDK又在寫B(tài)ug!告訴你為何Java NIO的ByteBuffer這么垃圾!

      通過在某個對象所持有的資源不再被其他對象引用時釋放該對象所持有的資源來優(yōu)化內(nèi)存使用和性能的技術(shù)。

      Netty 在4.x為 ByteBuf 和 ByteBufHolder 帶來了引用計數(shù)技術(shù),都實現(xiàn)了:

      ReferenceCounted接口

      需要顯式釋放的引用計數(shù)對象。

      當(dāng)一個新的ReferenceCounted被實例化時,以1 作為初始值。

      增加引用計數(shù),將引用計數(shù)加1。只要引用計數(shù)>0,就能保證對象不會被釋放。

      減少引用計數(shù),將引用計數(shù)減1。若引用計數(shù)減少到0 ,對象將被顯式釋放,并且訪問釋放的對象通常會導(dǎo)致訪問沖突。

      若實現(xiàn)ReferenceCounted的對象是其他實現(xiàn)ReferenceCounted的對象的容器,則當(dāng)容器的引用計數(shù)變?yōu)?0 時,所包含的對象也將通過release()被釋放。

      引用計數(shù)對于池化實現(xiàn)(如 PooledByteBufAllocator)很重要,它降低了內(nèi)存分配的開銷。

      Channel channel = ...; // 從 Channel 獲取 ByteBufAllocator ByteBufAllocator allocator = channel.alloc(); ... // 從 ByteBufAllocator 分配一個 ByteBuf ByteBuf buffer = allocator.directBuffer(); // 檢查引用計數(shù)是否為預(yù)期的 1 assert buffer.refCnt() == 1; ByteBuf buffer = ...; // 減少該對象的活動引用。當(dāng)減少到 0 時,該對象被釋放,該方法返回 true boolean released = buffer.release();

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      試圖訪問一個已經(jīng)被釋放的引用計數(shù)的對象,將會拋IllegalReferenceCountException

      一個特定的(ReferenceCounted 的實現(xiàn))類,可以用它自己的獨(dú)特方式來定義它的引用計數(shù)規(guī)則。例如可以設(shè)想一個類,其 release()方法的實現(xiàn)總是將引用計數(shù)設(shè)為

      零,而不用關(guān)心它的當(dāng)前值,從而一次性使所有的活動引用都失效。

      誰負(fù)責(zé)釋放

      一般由最后訪問(引用計數(shù))對象的那一方來負(fù)責(zé)將它釋放。

      Java JDK 容器

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:如何使用wps表格進(jìn)行分班(wps排班表怎么做)
      下一篇:使用 FocusScopeNode 在 TextFormFields 之間輕松移動焦點
      相關(guān)文章
      亚洲国产精品成人综合色在线| 亚洲无mate20pro麻豆| 亚洲爆乳无码专区www| 亚洲日本国产乱码va在线观看| 亚洲男人天堂2017| 亚洲国产老鸭窝一区二区三区| 国产成人无码综合亚洲日韩 | 亚洲日韩国产精品乱| 亚洲国产成人久久综合区| 午夜亚洲国产成人不卡在线 | 内射干少妇亚洲69XXX| 亚洲国产综合精品中文第一区| 亚洲av中文无码乱人伦在线播放 | 亚洲视频无码高清在线| 中文字幕亚洲情99在线| 最新国产精品亚洲| 国产亚洲精品bv在线观看| 亚洲精品无码专区| 国产成人 亚洲欧洲| 狠狠入ady亚洲精品| 亚洲不卡无码av中文字幕| 亚洲成A∨人片天堂网无码| 亚洲国产成人久久综合一区77| 亚洲一级特黄大片无码毛片 | 国产99在线|亚洲| 在线观看亚洲AV日韩AV| 亚洲av无码成人精品区一本二本| 亚洲国产av玩弄放荡人妇| 国产精品亚洲а∨无码播放不卡 | 国产亚洲?V无码?V男人的天堂| 亚洲精品无码久久久久| 亚洲天堂中文资源| 亚洲精品美女视频| 亚洲AV成人噜噜无码网站| 亚洲熟妇AV一区二区三区宅男| 亚洲av日韩av永久无码电影 | 精品久久久久久亚洲精品| 亚洲码欧美码一区二区三区| 亚洲AV网站在线观看| 国产午夜亚洲不卡| 亚洲最新视频在线观看|