日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

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

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

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

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

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

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

可能有一些讀者并沒有關(guān)注賽題,我這里簡化一下前言,可以直接理解為:有一塊 2G 的 HeapByteBuffer 用于文件 IO,我們該如何利用。

HeapByteBuffer 的復制問題

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

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

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)場景,這個行為通常都是多線程的,例如在云原生編程挑戰(zhàn)賽的評測下,有 40 個線程進行讀寫,如果按照線程維度進行緩存,每個線程分到 50M 用于內(nèi)存緩存自然是沒有問題。

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

FileChannel 使用的是 IOUtil 進行讀寫操作

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)當使用 HeapByteBuffer 時,會走到下面這個分支

Util.getTemporaryDirectBuffer(var1.remaining());
這個 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 這個方法會根據(jù)傳入 Buffer 的大小決定如何分配堆外內(nèi)存,如果過大,直接分配大緩沖區(qū);如果不是太大,會使用 bufferCache 這個 ThreadLocal 變量來進行緩存,從而復用(實際上這個數(shù)值非常大,幾乎不會走進直接分配堆外內(nèi)存這個分支)。這么看來似乎發(fā)現(xiàn)了兩個不得了的結(jié)論:

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

為什么 HeapByteBuffer 在 IO 時需要復制到 DirectByteBuffer

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

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

解決方案

其實我們本質(zhì)上是為了給每個線程維護一塊 HeapByteBuffer,用于緩存數(shù)據(jù),并沒有必要以 ByteBuffer 的大小為維度來進行 IO。可以借鑒 IOUtil 中復制 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)存的復制,但我們可以自己復制,從而使得這個過程更加直觀地被我們自己操控,而不是被 FileChannel 的內(nèi)部邏輯左右。

這里也需要注意

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

其他注意事項

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

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

總結(jié)

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


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

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅(qū)動電源設(shè)計中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計成為提升電機驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實際應用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設(shè)計、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉