Java NIO——零拷貝

什么是零拷貝

WIKI中對其有如下定義:

"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.

從WIKI的定義中解幽,我們看到“零拷貝”是指計算機操作的過程中剪验,CPU不需要為數(shù)據(jù)在內(nèi)存之間的拷貝消耗資源。而它通常是指計算機在網(wǎng)絡上發(fā)送文件時燎孟,不需要將文件內(nèi)容拷貝到用戶空間(User Space)而直接在內(nèi)核空間(Kernel Space)中傳輸?shù)骄W(wǎng)絡的方式坚俗。

零拷貝給我們帶來的好處

  • 減少甚至完全避免不必要的CPU拷貝镜盯,從而讓CPU解脫出來去執(zhí)行其他的任務。
  • 減少內(nèi)存帶寬的占用猖败。
  • 通常零拷貝技術(shù)還能夠減少用戶空間和操作系統(tǒng)內(nèi)核空間之間的上下文切換速缆。

零拷貝的實現(xiàn)

零拷貝實際的實現(xiàn)并沒有真正的標準,取決于操作系統(tǒng)如何實現(xiàn)這一點恩闻。零拷貝完全依賴于操作系統(tǒng)艺糜。操作系統(tǒng)支持,就有幢尚;不支持破停,就沒有。不依賴Java本身尉剩。

傳統(tǒng)I/O

在Java中真慢,我們可以通過InputStream從源數(shù)據(jù)中讀取數(shù)據(jù)流到一個緩沖區(qū)里,然后再將它們輸入到OutputStream里理茎。我們知道黑界,這種IO方式傳輸效率是比較低的。那么皂林,當使用上面的代碼時操作系統(tǒng)會發(fā)生什么情況:


這是一個從磁盤文件讀取并且通過socket寫出的過程朗鸠,對應的系統(tǒng)調(diào)用如下:

read(file,tmp_buf,len)
write(socket,tmp_buf,len)
  • 1、程序使用read()系統(tǒng)調(diào)用式撼。系統(tǒng)由用戶態(tài)轉(zhuǎn)換為內(nèi)核態(tài)(第一次上下文切換)童社,磁盤中的數(shù)據(jù)有DMA(Direct Memory Access)的方式讀取到內(nèi)核緩沖區(qū)(kernel buffer)求厕。DMA過程中CPU不需要參與數(shù)據(jù)的讀寫著隆,而是DMA處理器直接將硬盤數(shù)據(jù)通過總線傳輸?shù)絻?nèi)存中。

  • 2呀癣、系統(tǒng)由內(nèi)核態(tài)轉(zhuǎn)換為用戶態(tài)(第二次上下文切換)美浦,當程序要讀取的數(shù)據(jù)已經(jīng)完成寫入內(nèi)核緩沖區(qū)以后,程序會將數(shù)據(jù)由內(nèi)核緩存區(qū)项栏,寫入用戶緩存區(qū))浦辨,這個過程需要CPU參與數(shù)據(jù)的讀寫。

  • 3沼沈、程序使用write()系統(tǒng)調(diào)用流酬。系統(tǒng)由用戶態(tài)切換到內(nèi)核態(tài)(第三次上下文切換)币厕,數(shù)據(jù)從用戶態(tài)緩沖區(qū)寫入到網(wǎng)絡緩沖區(qū)(Socket Buffer),這個過程需要CPU參與數(shù)據(jù)的讀寫芽腾。

  • 4旦装、系統(tǒng)由內(nèi)核態(tài)切換到用戶態(tài)(第四次上下文切換),網(wǎng)絡緩沖區(qū)的數(shù)據(jù)通過DMA的方式傳輸?shù)骄W(wǎng)卡的驅(qū)動(存儲緩沖區(qū))中(protocol engine)

可以看到摊滔,傳統(tǒng)的I/O方式會經(jīng)過4次用戶態(tài)和內(nèi)核態(tài)的切換(上下文切換)阴绢,兩次CPU中內(nèi)存中進行數(shù)據(jù)讀寫的過程。這種拷貝過程相對來說比較消耗資源艰躺。

mmap內(nèi)存映射方式I/O

mmap 通過內(nèi)存映射呻袭,將文件映射到內(nèi)核緩沖區(qū),同時腺兴,用戶空間可以共享內(nèi)核空間的數(shù)據(jù)左电。這樣,在進行網(wǎng)絡傳輸時含长,就可以減少內(nèi)核空間到用戶控件的拷貝次數(shù)券腔。

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);

這是使用的系統(tǒng)調(diào)用方法,這種方式的I/O原理就是將用戶緩沖區(qū)(user buffer)的內(nèi)存地址和內(nèi)核緩沖區(qū)(kernel buffer)的內(nèi)存地址做一個映射拘泞,也就是說系統(tǒng)在用戶態(tài)可以直接讀取并操作內(nèi)核空間的數(shù)據(jù)纷纫。

  • 1、mmap()系統(tǒng)調(diào)用首先會使用DMA的方式將磁盤數(shù)據(jù)讀取到內(nèi)核緩沖區(qū)陪腌,然后通過內(nèi)存映射的方式辱魁,使用戶緩沖區(qū)和內(nèi)核讀緩沖區(qū)的內(nèi)存地址為同一內(nèi)存地址,也就是說不需要CPU再講數(shù)據(jù)從內(nèi)核讀緩沖區(qū)復制到用戶緩沖區(qū)诗鸭。

  • 2染簇、當使用write()系統(tǒng)調(diào)用的時候,cpu將內(nèi)核緩沖區(qū)(等同于用戶緩沖區(qū))的數(shù)據(jù)直接寫入到網(wǎng)絡發(fā)送緩沖區(qū)(socket buffer)强岸,然后通過DMA的方式將數(shù)據(jù)傳入到網(wǎng)卡驅(qū)動程序中準備發(fā)送锻弓。

可以看到這種內(nèi)存映射的方式減少了CPU的讀寫次數(shù),但是用戶態(tài)到內(nèi)核態(tài)的切換(上下文切換)依舊有四次蝌箍,同時需要注意在進行這種內(nèi)存映射的時候青灼,有可能會出現(xiàn)并發(fā)線程操作同一塊內(nèi)存區(qū)域而導致的嚴重的數(shù)據(jù)不一致問題,所以需要進行合理的并發(fā)編程來解決這些問題妓盲。

通過sendfile實現(xiàn)的零拷貝I/O

Linux 2.1 版本 提供了 sendFile 函數(shù)杂拨,其基本原理如下:數(shù)據(jù)根本不經(jīng)過用戶態(tài),直接從內(nèi)核緩沖區(qū)進入到 Socket Buffer悯衬,同時弹沽,由于和用戶態(tài)完全無關(guān),就減少了一次上下文切換。

sendfile(socket, file, len);

通過sendfile()系統(tǒng)調(diào)用策橘,可以做到內(nèi)核空間內(nèi)部直接進行I/O傳輸炸渡。

  • 1、sendfile()系統(tǒng)調(diào)用也會引起用戶態(tài)到內(nèi)核態(tài)的切換丽已,與內(nèi)存映射方式不同的是偶摔,用戶空間此時是無法看到或修改數(shù)據(jù)內(nèi)容,也就是說這是一次完全意義上的數(shù)據(jù)傳輸過程促脉。

  • 2辰斋、從磁盤讀取到內(nèi)存是DMA的方式,從內(nèi)核讀緩沖區(qū)讀取到網(wǎng)絡發(fā)送緩沖區(qū)瘸味,依舊需要CPU參與拷貝宫仗,而從網(wǎng)絡發(fā)送緩沖區(qū)到網(wǎng)卡中的緩沖區(qū)依舊是DMA方式。

sendfile()系統(tǒng)調(diào)用旁仿,依舊有一次CPU進行數(shù)據(jù)拷貝藕夫,兩次用戶態(tài)和內(nèi)核態(tài)的切換操作,相比較于內(nèi)存映射的方式有了很大的進步枯冈,但問題是程序不能對數(shù)據(jù)進行修改毅贮,而只是單純地進行了一次數(shù)據(jù)的傳輸過程。

理想狀態(tài)下的零拷貝I/O

Linux 在 2.4 版本中尘奏,做了一些修改滩褥,避免了從內(nèi)核緩沖區(qū)拷貝到 Socket buffer 的操作,直接拷貝到協(xié)議棧炫加,從而再一次減少了數(shù)據(jù)拷貝瑰煎。

依舊是系統(tǒng)調(diào)用sendfile()

sendfile(socket, file, len);

可以看到,這是真正意義上的零拷貝俗孝,因為其間CPU已經(jīng)不參與數(shù)據(jù)的拷貝過程酒甸,也就是說完全通過其他硬件和中斷的方式來實現(xiàn)數(shù)據(jù)的讀寫過程嗎,但是這樣的過程需要硬件的支持才能實現(xiàn)赋铝。

借助于硬件上的幫助插勤,我們是可以辦到的。之前我們是把頁緩存的數(shù)據(jù)拷貝到socket緩存中革骨,實際上农尖,我們僅僅需要把緩沖區(qū)描述符傳到socket緩沖區(qū),再把數(shù)據(jù)長度傳過去苛蒲,這樣DMA控制器直接將頁緩存中的數(shù)據(jù)打包發(fā)送到網(wǎng)絡中就可以了卤橄。

  • 1绿满、系統(tǒng)調(diào)用sendfile()發(fā)起后臂外,磁盤數(shù)據(jù)通過DMA方式讀取到內(nèi)核緩沖區(qū),內(nèi)核緩沖區(qū)中的數(shù)據(jù)通過DMA聚合網(wǎng)絡緩沖區(qū),然后一齊發(fā)送到網(wǎng)卡中漏健。

可以看到在這種模式下嚎货,是沒有一次CPU進行數(shù)據(jù)拷貝的,所以就做到了真正意義上的零拷貝蔫浆,雖然和前一種是同一個系統(tǒng)調(diào)用殖属,但是這種模式實現(xiàn)起來需要硬件的支持,但對于基于操作系統(tǒng)的用戶來講瓦盛,操作系統(tǒng)已經(jīng)屏蔽了這種差異洗显,它會根據(jù)不同的硬件平臺來實現(xiàn)這個系統(tǒng)調(diào)用。

零拷貝的再次理解

  • 1原环、我們說零拷貝挠唆,是從操作系統(tǒng)的角度來說的。因為內(nèi)核緩沖區(qū)之間嘱吗,沒有數(shù)據(jù)是重復的(只有 kernel buffer 有一份數(shù)據(jù))玄组。

  • 2、零拷貝不僅僅帶來更少的數(shù)據(jù)復制谒麦,還能帶來其他的性能優(yōu)勢俄讹,例如更少的上下文切換弃榨,更少的 CPU 緩存?zhèn)喂蚕硪约盁o CPU 校驗和計算瘟忱。

mmap 和 sendFile 的區(qū)別

  • 1佑吝、mmap 適合小數(shù)據(jù)量讀寫红选,sendFile 適合大文件傳輸晴股。

  • 2海雪、mmap 需要 4 次上下文切換锋叨,3 次數(shù)據(jù)拷貝含鳞;sendFile 需要 3 次上下文切換城丧,最少 2 次數(shù)據(jù)拷貝延曙。

  • 2、sendFile 可以利用 DMA 方式亡哄,減少 CPU 拷貝枝缔,mmap 則不能(必須從內(nèi)核拷貝到 Socket 緩沖區(qū))。

Java NIO的零拷貝

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost",7001));
String filename = "text.zip";
//得到一個文件channel
FileChannel fileChannel = new FileInputStream(filename).getChannel();

//在linux下一個transferTo 方法就可以完成傳輸
//在windows下 一次調(diào)用 transferTo 只能發(fā)送8M, 大文件就需要分段傳輸文件
//transferTo 底層使用到零拷貝
long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
//關(guān)閉
fileChannel.close();

NIO的零拷貝由transferTo()方法實現(xiàn)蚊惯。transferTo()方法將數(shù)據(jù)從FileChannel對象傳送到可寫的字節(jié)通道(如Socket Channel等)愿卸。在內(nèi)部實現(xiàn)中,由native方法transferTo0()來實現(xiàn)截型,它依賴底層操作系統(tǒng)的支持趴荸。在UNIX和Linux系統(tǒng)中,調(diào)用這個方法將會引起sendfile()系統(tǒng)調(diào)用宦焦。

使用場景一般是:

  • 1发钝、較大顿涣,讀寫較慢,追求速度酝豪。
  • 2涛碑、M內(nèi)存不足,不能加載太大數(shù)據(jù)孵淘。
  • 3蒲障、帶寬不夠,即存在其他程序或線程存在大量的IO操作瘫证,導致帶寬本來就小揉阎。

以上都建立在不需要進行數(shù)據(jù)文件操作的情況下,如果既需要這樣的速度背捌,也需要進行數(shù)據(jù)操作怎么辦余黎?
那么使用NIO的直接內(nèi)存!

NIO的直接內(nèi)存

File file = new File("test.zip");
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");

//獲取對應的通道
FileChannel fileChannel = randomAccessFile.getChannel();

/**
 * 參數(shù)1:FileChannel.MapMode.READ_WRITE 使用的讀寫模式
 * 參數(shù)2:可以直接修改的起始位置
 * 參數(shù)3: 是映射到內(nèi)存的大小(不是索引位置) ,即將 test.zip 的多少個字節(jié)映射到內(nèi)存
 * 實際類型 DirectByteBuffer
 */
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size);

首先载萌,它的作用位置處于傳統(tǒng)IO(BIO)與零拷貝之間惧财,為何這么說?

  • 1扭仁、IO垮衷,可以把磁盤的文件經(jīng)過內(nèi)核空間,讀到JVM空間乖坠,然后進行各種操作搀突,最后再寫到磁盤或是發(fā)送到網(wǎng)絡,效率較慢但支持數(shù)據(jù)文件操作熊泵。

  • 2仰迁、零拷貝則是直接在內(nèi)核空間完成文件讀取并轉(zhuǎn)到磁盤(或發(fā)送到網(wǎng)絡)。由于它沒有讀取文件數(shù)據(jù)到JVM這一環(huán)顽分,因此程序無法操作該文件數(shù)據(jù)徐许,盡管效率很高!

而直接內(nèi)存則介于兩者之間卒蘸,效率一般且可操作文件數(shù)據(jù)雌隅。直接內(nèi)存(mmap技術(shù))將文件直接映射到內(nèi)核空間的內(nèi)存,返回一個操作地址(address)缸沃,它解決了文件數(shù)據(jù)需要拷貝到JVM才能進行操作的窘境恰起。而是直接在內(nèi)核空間直接進行操作,省去了內(nèi)核空間拷貝到用戶空間這一步操作趾牧。

NIO的直接內(nèi)存是由MappedByteBuffer實現(xiàn)的检盼。核心即是map()方法,該方法把文件映射到內(nèi)存中翘单,獲得內(nèi)存地址addr吨枉,然后通過這個addr構(gòu)造MappedByteBuffer類蹦渣,以暴露各種文件操作API。

由于MappedByteBuffer申請的是堆外內(nèi)存东羹,因此不受Minor GC控制,只能在發(fā)生Full GC時才能被回收忠烛。而DirectByteBuffer改善了這一情況属提,它是MappedByteBuffer類的子類,同時它實現(xiàn)了DirectBuffer接口美尸,維護一個Cleaner對象來完成內(nèi)存回收冤议。因此它既可以通過Full GC來回收內(nèi)存,也可以調(diào)用clean()方法來進行回收师坎。

另外恕酸,直接內(nèi)存的大小可通過jvm參數(shù)來設(shè)置:-XX:MaxDirectMemorySize

NIO的MappedByteBuffer還有一個兄弟叫做HeapByteBuffer胯陋。顧名思義蕊温,它用來在堆中申請內(nèi)存,本質(zhì)是一個數(shù)組遏乔。由于它位于堆中义矛,因此可受GC管控,易于回收盟萨。

參考:
https://www.cnblogs.com/eryun/p/12088001.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凉翻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捻激,更是在濱河造成了極大的恐慌制轰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胞谭,死亡現(xiàn)場離奇詭異垃杖,居然都是意外死亡,警方通過查閱死者的電腦和手機丈屹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門缩滨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泉瞻,你說我怎么就攤上這事脉漏。” “怎么了袖牙?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵侧巨,是天一觀的道長。 經(jīng)常有香客問我鞭达,道長司忱,這世上最難降的妖魔是什么皇忿? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮坦仍,結(jié)果婚禮上鳍烁,老公的妹妹穿的比我還像新娘。我一直安慰自己繁扎,他們只是感情好幔荒,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梳玫,像睡著了一般爹梁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上提澎,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天姚垃,我揣著相機與錄音,去河邊找鬼盼忌。 笑死积糯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谦纱。 我是一名探鬼主播絮宁,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼服协!你這毒婦竟也來了绍昂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤偿荷,失蹤者是張志新(化名)和其女友劉穎窘游,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跳纳,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡忍饰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了寺庄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艾蓝。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖斗塘,靈堂內(nèi)的尸體忽然破棺而出赢织,到底是詐尸還是另有隱情,我是刑警寧澤馍盟,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布于置,位于F島的核電站,受9級特大地震影響贞岭,放射性物質(zhì)發(fā)生泄漏八毯。R本人自食惡果不足惜搓侄,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望话速。 院中可真熱鬧讶踪,春花似錦、人聲如沸泊交。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽活合。三九已至雏婶,卻和暖如春物赶,著一層夾襖步出監(jiān)牢的瞬間白指,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工酵紫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留告嘲,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓奖地,卻偏偏與公主長得像橄唬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子参歹,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容