當(dāng)前位置:首頁 > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]分享一個(gè)很多人容易踩的一個(gè)坑:HeapByteBuffer的使用問題。我們都知道NIO分裝了ByteBuffer接口,使得filechannel的文件IOAPI變得非常的簡(jiǎn)單。ByteBuffer主要有兩個(gè)實(shí)現(xiàn)類HeapByteBuffer堆內(nèi)內(nèi)存DirectByteBuffer...

分享一個(gè)很多人容易踩的一個(gè)坑:HeapByteBuffer 的使用問題。我們都知道 NIO 分裝了 ByteBuffer 接口,使得 filechannel 的文件 IO API 變得非常的簡(jiǎn)單。ByteBuffer 主要有兩個(gè)實(shí)現(xiàn)類

  • HeapByteBuffer 堆內(nèi)內(nèi)存
  • DirectByteBuffer 堆外內(nèi)存
按我的個(gè)人經(jīng)驗(yàn),大多數(shù)情況,無論是讀操作還是寫操作,我都傾向于使用 DirectByteBuffer,主要是因?yàn)?HeapByteBuffer 在和 FileChannel 交互時(shí),可能會(huì)有一些出乎大家意料的內(nèi)部操作,也就是這篇文章的標(biāo)題中提到的注意事項(xiàng),這里先賣個(gè)關(guān)子。

先來看看這次比賽為什么要用到 HeapByteBuffer 呢?

原因一:賽題需要設(shè)計(jì)分級(jí)存儲(chǔ),并且提供了 6G 堆內(nèi)內(nèi)存 2G 堆外內(nèi)存,一個(gè)最直接的思路便是使用內(nèi)存來存儲(chǔ)熱點(diǎn)數(shù)據(jù),而內(nèi)存存儲(chǔ)數(shù)據(jù)最方便的數(shù)據(jù)結(jié)構(gòu)便是 ByteBuffer 了。

原因二:由于堆內(nèi) 6G 遠(yuǎn)大于堆外 2G,且 JVM 參數(shù)不能調(diào)整,所以要想利用好堆內(nèi)富余的內(nèi)存去做緩存,非 HeapByteBuffer 莫屬了。

可能有一些讀者并沒有關(guān)注賽題,我這里簡(jiǎn)化一下前言,可以直接理解為:有一塊 2G 的 HeapByteBuffer 用于文件 IO,我們?cè)撊绾卫谩?/p>

HeapByteBuffer 的復(fù)制問題

廢話不多說,直接來看 HeapByteBuffer 的坑在哪兒。

使用代碼描述 HeapByteBuffer 的文件 IO 操作,大概率會(huì)寫出如下的代碼:

public?void?readInOneThread()?throws?Exception?{
????int?bufferSize?=?50?*?1024?*?1024;
????File?file?=?new?File("/essd");
????FileChannel?fileChannel?=?new?RandomAccessFile(file,?"rw").getChannel();
????ByteBuffer?byteBuffer?=?ByteBuffer.allocate(bufferSize);
????fileChannel.read(byteBuffer);
}
上述的代碼,將文件中的數(shù)據(jù)緩存到了內(nèi)存中,無論是賽題還是生產(chǎn)場(chǎng)景,這個(gè)行為通常都是多線程的,例如在云原生編程挑戰(zhàn)賽的評(píng)測(cè)下,有 40 個(gè)線程進(jìn)行讀寫,如果按照線程維度進(jìn)行緩存,每個(gè)線程分到 50M 用于內(nèi)存緩存自然是沒有問題。

而如果你直接使用上述代碼,在評(píng)測(cè)中可能會(huì)直接得到內(nèi)存溢出相關(guān)的異常。其實(shí)我在之前堆外內(nèi)存泄漏的文章中也提到過這個(gè)問題,不過角度有所不同。原因很簡(jiǎn)單,直接來看源碼。

FileChannel 使用的是 IOUtil 進(jìn)行讀寫操作

static?int?read(FileDescriptor?var0,?ByteBuffer?var1,?long?var2,?NativeDispatcher?var4)?throws?IOException?{
????if?(var1.isReadOnly())?{
????????throw?new?IllegalArgumentException("Read-only?buffer");
????}?else?if?(var1?instanceof?DirectBuffer)?{
????????return?readIntoNativeBuffer(var0,?var1,?var2,?var4);
????}?else?{
????????ByteBuffer?var5?=?Util.getTemporaryDirectBuffer(var1.remaining());
????????int?var7;
????????try?{
????????????int?var6?=?readIntoNativeBuffer(var0,?var5,?var2,?var4);
????????????var5.flip();
????????????if?(var6?>?0)?{
????????????????var1.put(var5);
????????????}
????????????var7?=?var6;
????????}?finally?{
????????????Util.offerFirstTemporaryDirectBuffer(var5);
????????}
????????return?var7;
????}
}
可以發(fā)現(xiàn)當(dāng)使用 HeapByteBuffer 時(shí),會(huì)走到下面這個(gè)分支

Util.getTemporaryDirectBuffer(var1.remaining());
這個(gè) Util 封裝了更為底層的一些 IO 邏輯

package?sun.nio.ch;
public?class?Util?{
????private?static?ThreadLocal?bufferCache;
????
????public?static?ByteBuffer?getTemporaryDirectBuffer(int?var0)?{
????????if?(isBufferTooLarge(var0))?{
????????????return?ByteBuffer.allocateDirect(var0);
????????}?else?{
????????????//?FOUCS?ON?THIS?LINE
????????????Util.BufferCache?var1?=?(Util.BufferCache)bufferCache.get();
????????????ByteBuffer?var2?=?var1.get(var0);
????????????if?(var2?!=?null)?{
????????????????return?var2;
????????????}?else?{
????????????????if?(!var1.isEmpty())?{
????????????????????var2?=?var1.removeFirst();
????????????????????free(var2);
????????????????}

????????????????return?ByteBuffer.allocateDirect(var0);
????????????}
????????}
????}
}
isBufferTooLarge 這個(gè)方法會(huì)根據(jù)傳入 Buffer 的大小決定如何分配堆外內(nèi)存,如果過大,直接分配大緩沖區(qū);如果不是太大,會(huì)使用 bufferCache 這個(gè) ThreadLocal 變量來進(jìn)行緩存,從而復(fù)用(實(shí)際上這個(gè)數(shù)值非常大,幾乎不會(huì)走進(jìn)直接分配堆外內(nèi)存這個(gè)分支)。這么看來似乎發(fā)現(xiàn)了兩個(gè)不得了的結(jié)論:

  1. 使用 HeapByteBuffer 讀寫都會(huì)經(jīng)過 DirectByteBuffer,寫入數(shù)據(jù)的流轉(zhuǎn)方式其實(shí)是:HeapByteBuffer -> DirectByteBuffer -> PageCache -> Disk,讀取數(shù)據(jù)的流轉(zhuǎn)方式正好相反。
  2. 使用 HeapByteBuffer 讀寫會(huì)申請(qǐng)一塊跟線程綁定的 DirectByteBuffer。這意味著,線程越多,臨時(shí) DirectByteBuffer 就越會(huì)占用越多的空間。
根據(jù)這兩個(gè)結(jié)論,我們?cè)倩氐劫愵}中,如果直接按照上述的方式進(jìn)行讀寫,40 個(gè)線程每個(gè)都持有一個(gè) 50M 的堆內(nèi)內(nèi)存,同時(shí)又因?yàn)?IOUtil ?的內(nèi)部行為,額外分配了 40*50M 的堆外內(nèi)存, 堆外內(nèi)存在不經(jīng)意間就被用光了!出現(xiàn)堆外內(nèi)存溢出的異常也就不奇怪了。

為什么 HeapByteBuffer 在 IO 時(shí)需要復(fù)制到 DirectByteBuffer

這個(gè)我之前也介紹過,詳情可以參考我的一篇舊文:《一文探討堆外內(nèi)存的監(jiān)控與回收》??偨Y(jié)如下:

  • 為了方便 GC 的實(shí)現(xiàn),DirectByteBuffer 指向的 native memory 是不受 GC 管轄的
  • HeapByteBuffer 背后使用的是 byte 數(shù)組,其占用的內(nèi)存不一定是連續(xù)的,不太方便 JNI 方法的調(diào)用
  • 數(shù)組實(shí)現(xiàn)在不同 JVM 中可能會(huì)不同

解決方案

其實(shí)我們本質(zhì)上是為了給每個(gè)線程維護(hù)一塊 HeapByteBuffer,用于緩存數(shù)據(jù),并沒有必要以 ByteBuffer 的大小為維度來進(jìn)行 IO??梢越梃b IOUtil 中復(fù)制 DirectByteBuffer 的思路來優(yōu)化這一過程。代碼示例如下:

public?void?directBufferCopy()?throws?Exception?{
????File?file?=?new?File("/essd");
????FileChannel?fileChannel?=?new?RandomAccessFile(file,?"rw").getChannel();
????ByteBuffer?byteBuffer?=?ByteBuffer.allocate(50?*?1024?*?1024);
????ByteBuffer?directByteBuffer?=?ByteBuffer.allocateDirect(4?*?1024);
????for?(int?i?=?0;?i?12800;?i )?{
????????directByteBuffer.clear();
????????fileChannel.read(directByteBuffer,?i?*?4?*?1024);
????????directByteBuffer.flip();
????????byteBuffer.put(directByteBuffer);
????}
}
在 Java 中,從磁盤到堆內(nèi)內(nèi)存,一定無法省略堆外內(nèi)存的復(fù)制,但我們可以自己復(fù)制,從而使得這個(gè)過程更加直觀地被我們自己操控,而不是被 FileChannel 的內(nèi)部邏輯左右。

這里也需要注意

  • 單次 IO 使用的 DirectByteBuffer 不宜過大,僅僅作為一個(gè)運(yùn)輸載體,起到一個(gè)運(yùn)輸數(shù)據(jù)的作用。這樣在多線程場(chǎng)景下,才不至于占用過多的堆外內(nèi)存
  • 單次 IO 使用的 DirectByteBuffer 不宜過小,否則會(huì)出現(xiàn)讀寫放大的問題,一般建議設(shè)置 4kb 的整數(shù)倍,具體以實(shí)際測(cè)試結(jié)果為準(zhǔn)。

其他注意事項(xiàng)

HeapByteBuffer 讀寫時(shí)的復(fù)制問題是本文的主角,但使用 HeapByteBuffer 作為緩存時(shí),也需要注意一些其他問題。例如比賽場(chǎng)景中,你可能希望開辟一大塊 HeapByteBuffer,6G 堆內(nèi)內(nèi)存,分配個(gè) 4G 用作緩存總可以吧?可不可以我說了不算,你感興趣的話倒是可以測(cè)試一下是否可行,還需要考慮 GC 情況,需要綜合考慮老年代和新生代的配比,如果你分配了過多堆內(nèi)內(nèi)存給 HeapByteBuffer 緩存,可能會(huì)直接導(dǎo)致 OutOfMemory 或者觸發(fā) GC。

同時(shí),如果 HeapByteBuffer 占用了過多內(nèi)存,留給操作系統(tǒng)的 PageCache 也會(huì)非常有限,這兩者使用的可是同一塊內(nèi)存!如果你的程序利用到了 PageCache 的特性,可能會(huì)由于 PageCache 空間不夠,導(dǎo)致 IO 速度變慢。

總結(jié)

本文介紹了在文件 IO 中使用 HeapByteBuffer 的注意事項(xiàng),需要考慮到 FileChannel 內(nèi)部的復(fù)制問題,意識(shí)到這一過程會(huì)有堆外內(nèi)存的復(fù)制開銷。在實(shí)際使用場(chǎng)景中,個(gè)人更加推薦直接使用 DirectByteBuffer 進(jìn)行 IO 操作。如果出于某些原因,一定需要使用 HeapByteBuffer 存儲(chǔ)作為緩存,可以參考文中分批使用 DirectByteBuffer 進(jìn)行 IO 并復(fù)制的方案。


本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉