国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

JAVA IO 以及 NIO 理解

壞嘻嘻
發(fā)布: 2018-09-14 09:23:24
原創(chuàng)
2050人瀏覽過

由于netty,了解了一些異步io的知識,java里面nio就是原來的io的一個補(bǔ)充,本文主要記錄下在java中io的底層實(shí)現(xiàn)原理,以及對zerocopy技術(shù)介紹。

IO,其實(shí)意味著:數(shù)據(jù)不停地搬入搬出緩沖區(qū)而已(使用了緩沖區(qū))。比如,用戶程序發(fā)起讀操作,導(dǎo)致“ syscall read ”系統(tǒng)調(diào)用,就會把數(shù)據(jù)搬入到 一個buffer中;用戶發(fā)起寫操作,導(dǎo)致 “syscall write ”系統(tǒng)調(diào)用,將會把一個 buffer 中的數(shù)據(jù) 搬出去(發(fā)送到網(wǎng)絡(luò)中 or 寫入到磁盤文件)

上面的過程看似簡單,但是底層操作系統(tǒng)具體如何實(shí)現(xiàn)以及實(shí)現(xiàn)的細(xì)節(jié)就非常復(fù)雜了。正是因?yàn)閷?shí)現(xiàn)方式不同,有針對普通情況下的文件傳輸(暫且稱普通IO吧),也有針對大文件傳輸或者批量大數(shù)據(jù)傳輸?shù)膶?shí)現(xiàn)方式,比如zerocopy技術(shù)。

整個IO過程的流程如下:

1)程序員寫代碼創(chuàng)建一個緩沖區(qū)(這個緩沖區(qū)是用戶緩沖區(qū)):哈哈。然后在一個while循環(huán)里面調(diào)用read()方法讀數(shù)據(jù)(觸發(fā)”syscall read”系統(tǒng)調(diào)用)

byte[] b = new byte[4096];
while((read = inputStream.read(b))>=0) { 
        total = total + read; 
            // other code…. 
        }
登錄后復(fù)制


2)當(dāng)執(zhí)行到read()方法時,其實(shí)底層是發(fā)生了很多操作的:

立即學(xué)習(xí)Java免費(fèi)學(xué)習(xí)筆記(深入)”;

①內(nèi)核給磁盤控制器發(fā)命令說:我要讀磁盤上的某某塊磁盤塊上的數(shù)據(jù)。–kernel issuing a command to the disk controller hardware to fetch the data from disk.

②在DMA的控制下,把磁盤上的數(shù)據(jù)讀入到內(nèi)核緩沖區(qū)。–The disk controller writes the data directly into a kernel memory buffer by DMA

③內(nèi)核把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶緩沖區(qū)。–kernel copies the data from the temporary buffer in kernel space

這里的用戶緩沖區(qū)應(yīng)該就是我們寫的代碼中 new 的 byte[] 數(shù)組。

從上面的步驟中可以分析出什么?

?對于操作系統(tǒng)而言,JVM只是一個用戶進(jìn)程,處于用戶態(tài)空間中。而處于用戶態(tài)空間的進(jìn)程是不能直接操作底層的硬件的。而IO操作就需要操作底層的硬件,比如磁盤。因此,IO操作必須得借助內(nèi)核的幫助才能完成(中斷,trap),即:會有用戶態(tài)到內(nèi)核態(tài)的切換。

?我們寫代碼 new byte[] 數(shù)組時,一般是都是“隨意” 創(chuàng)建一個“任意大小”的數(shù)組。比如,new byte[128]、new byte[1024]、new byte[4096]….

但是,對于磁盤塊的讀取而言,每次訪問磁盤讀數(shù)據(jù)時,并不是讀任意大小的數(shù)據(jù)的,而是:每次讀一個磁盤塊或者若干個磁盤塊(這是因?yàn)樵L問磁盤操作代價是很大的,而且我們也相信局部性原理) 因此,就需要有一個“中間緩沖區(qū)”–即內(nèi)核緩沖區(qū)。先把數(shù)據(jù)從磁盤讀到內(nèi)核緩沖區(qū)中,然后再把數(shù)據(jù)從內(nèi)核緩沖區(qū)搬到用戶緩沖區(qū)。

這也是為什么我們總感覺到第一次read操作很慢,而后續(xù)的read操作卻很快的原因吧。因?yàn)椋瑢τ诤罄m(xù)的read操作而言,它所需要讀的數(shù)據(jù)很可能已經(jīng)在內(nèi)核緩沖區(qū)了,此時只需將內(nèi)核緩沖區(qū)中的數(shù)據(jù)拷貝到用戶緩沖區(qū)即可,并未涉及到底層的讀取磁盤操作,當(dāng)然就快了。

The kernel tries to cache and/or prefetch data, so the data being requested by the process may already be available in kernel space. 
If so, the data requested by the process is copied out.  
If the data isn’t available, the process is suspended while the kernel goes about bringing the data into memory.
登錄后復(fù)制


如果數(shù)據(jù)不可用,process將會被掛起,并需要等待內(nèi)核從磁盤上把數(shù)據(jù)取到內(nèi)核緩沖區(qū)中。

那我們可能會說:DMA為什么不直接將磁盤上的數(shù)據(jù)讀入到用戶緩沖區(qū)呢?一方面是 ?中提到的內(nèi)核緩沖區(qū)作為一個中間緩沖區(qū)。用來“適配”用戶緩沖區(qū)的“任意大小”和每次讀磁盤塊的固定大小。另一方面則是,用戶緩沖區(qū)位于用戶態(tài)空間,而DMA讀取數(shù)據(jù)這種操作涉及到底層的硬件,硬件一般是不能直接訪問用戶態(tài)空間的(OS的原因吧)

綜上,由于DMA不能直接訪問用戶空間(用戶緩沖區(qū)),普通IO操作需要將數(shù)據(jù)來回地在 用戶緩沖區(qū) 和 內(nèi)核緩沖區(qū)移動,這在一定程序上影響了IO的速度。那有沒有相應(yīng)的解決方案呢?

那就是直接內(nèi)存映射IO,也即JAVA NIO中提到的內(nèi)存映射文件,或者說 直接內(nèi)存….總之,它們表達(dá)的意思都差不多。內(nèi)核空間的 buffer 與 用戶空間的 buffer 都映射到同一塊 物理內(nèi)存區(qū)域。

它的主要特點(diǎn)如下:

①對文件的操作不需要再發(fā)read 或者 write 系統(tǒng)調(diào)用了—The user process sees the file data asmemory, so there is no need to issue read() or write() system calls.

②當(dāng)用戶進(jìn)程訪問“內(nèi)存映射文件”地址時,自動產(chǎn)生缺頁錯誤,然后由底層的OS負(fù)責(zé)將磁盤上的數(shù)據(jù)送到內(nèi)存。關(guān)于頁式存儲管理,可參考:內(nèi)存分配與內(nèi)存管理的一些理解

As the user process touches the mapped memory space, page faults will be generated automatically to bring in the file data from disk.  
If the user modifies the mapped memory space, the affected page is automatically marked as dirty and will be subsequently  
flushed to disk to update the file.
登錄后復(fù)制

這就是是JAVA NIO中提到的內(nèi)存映射緩沖區(qū)(Memory-Mapped-Buffer)它類似于JAVA NIO中的直接緩沖區(qū)(Directed Buffer)。MemoryMappedBuffer可以通過java.nio.channels.FileChannel.java(通道)的 map方法創(chuàng)建。

使用內(nèi)存映射緩沖區(qū)來操作文件,它比普通的IO操作讀文件要快得多。甚至比使用文件通道(FileChannel)操作文件 還要快。因?yàn)?,使用?nèi)存映射緩沖區(qū)操作文件時,沒有顯示的系統(tǒng)調(diào)用(read,write),而且OS還會自動緩存一些文件頁(memory page)

zerocopy技術(shù)介紹

看完了上面的IO操作的底層實(shí)現(xiàn)過程,再來了解zerocopy技術(shù)就很easy了。IBM有一篇名為《Efficient data transfer through zero copy》的論文對zerocopy做了完整的介紹。感覺非常好,下面就基于這篇文來記錄下自己的一些理解。

zerocopy技術(shù)的目標(biāo)就是提高IO密集型JAVA應(yīng)用程序的性能。在本文的前面部分介紹了:IO操作需要數(shù)據(jù)頻繁地在內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間拷貝,而zerocopy技術(shù)可以減少這種拷貝的次數(shù),同時也降低了上下文切換(用戶態(tài)與內(nèi)核態(tài)之間的切換)的次數(shù)。

比如,大多數(shù)WEB應(yīng)用程序執(zhí)行的一項(xiàng)操作就是:接受用戶請求—>從本地磁盤讀數(shù)據(jù)—>數(shù)據(jù)進(jìn)入內(nèi)核緩沖區(qū)—>用戶緩沖區(qū)—>內(nèi)核緩沖區(qū)—>用戶緩沖區(qū)—>socket發(fā)送

數(shù)據(jù)每次在內(nèi)核緩沖區(qū)與用戶緩沖區(qū)之間的拷貝會消耗CPU以及內(nèi)存的帶寬。而zerocopy有效減少了這種拷貝次數(shù)。

Each time data traverses the user-kernel boundary, it must be copied, which consumes CPU cycles and memory bandwidth.
Fortunately, you can eliminate these copies through a technique called—appropriately enough —zero copy

那它是怎么做到的呢?

我們知道,JVM(JAVA虛擬機(jī))為JAVA語言提供了跨平臺的一致性,屏蔽了底層操作系統(tǒng)的具體實(shí)現(xiàn)細(xì)節(jié),因此,JAVA語言也很難直接使用底層操作系統(tǒng)提供的一些“奇技淫巧”。

而要實(shí)現(xiàn)zerocopy,首先得有操作系統(tǒng)的支持。其次,JDK類庫也要提供相應(yīng)的接口支持。幸運(yùn)的是,自JDK1.4以來,JDK提供了對NIO的支持,通過java.nio.channels.FileChannel類的transferTo()方法可以直接將字節(jié)傳送到可寫的通道中(Writable Channel),并不需要將字節(jié)送入用戶程序空間(用戶緩沖區(qū))

You can use the transferTo()method to transfer bytes directly from the channel on which it is invoked to ?
another writable byte channel, without requiring data to flow through the application

下面就來詳細(xì)分析一下經(jīng)典的web服務(wù)器(比如文件服務(wù)器)干的活:從磁盤中中讀文件,并把文件通過網(wǎng)絡(luò)(socket)發(fā)送給Client。

File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);
從代碼上看,就是兩步操作。第一步:將文件讀入buf;第二步:將 buf 中的數(shù)據(jù)通過socket發(fā)送出去。但是,這兩步操作需要四次上下文切換(用戶態(tài)與內(nèi)核態(tài)之間的切換) 和 四次拷貝操作才能完成。

①第一次上下文切換發(fā)生在 read()方法執(zhí)行,表示服務(wù)器要去磁盤上讀文件了,這會導(dǎo)致一個 sys_read()的系統(tǒng)調(diào)用。此時由用戶態(tài)切換到內(nèi)核態(tài),完成的動作是:DMA把磁盤上的數(shù)據(jù)讀入到內(nèi)核緩沖區(qū)中(這也是第一次拷貝)。

②第二次上下文切換發(fā)生在read()方法的返回(這也說明read()是一個阻塞調(diào)用),表示數(shù)據(jù)已經(jīng)成功從磁盤上讀到內(nèi)核緩沖區(qū)了。此時,由內(nèi)核態(tài)返回到用戶態(tài),完成的動作是:將內(nèi)核緩沖區(qū)中的數(shù)據(jù)拷貝到用戶緩沖區(qū)(這是第二次拷貝)。

③第三次上下文切換發(fā)生在 send()方法執(zhí)行,表示服務(wù)器準(zhǔn)備把數(shù)據(jù)發(fā)送出去了。此時,由用戶態(tài)切換到內(nèi)核態(tài),完成的動作是:將用戶緩沖區(qū)中的數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)(這是第三次拷貝)

④第四次上下文切換發(fā)生在 send()方法的返回【這里的send()方法可以異步返回,所謂異步返回就是:線程執(zhí)行了send()之后立即從send()返回,剩下的數(shù)據(jù)拷貝及發(fā)送就交給底層操作系統(tǒng)實(shí)現(xiàn)了】。此時,由內(nèi)核態(tài)返回到用戶態(tài),完成的動作是:將內(nèi)核緩沖區(qū)中的數(shù)據(jù)送到 protocol engine.(這是第四次拷貝)

這里對 protocol engine不是太了解,但是從上面的示例圖來看:它是NIC(NetWork Interface Card) buffer。網(wǎng)卡的buffer???

下面這段話,非常值得一讀:這里再一次提到了為什么需要內(nèi)核緩沖區(qū)。

復(fù)制代碼
Use of the intermediate kernel buffer (rather than a direct transfer of the data
into the user buffer)might seem inefficient. But intermediate kernel buffers were ?
introduced into the process to improve performance. Using the intermediate ?
buffer on the read side allows the kernel buffer to act as a “readahead cache” ?
when the application hasn’t asked for as much data as the kernel buffer holds.
This significantly improves performance when the requested data amount is less
than the kernel buffer size. The intermediate buffer on the write side allows the write to complete asynchronously.
復(fù)制代碼
一個核心觀點(diǎn)就是:內(nèi)核緩沖區(qū)提高了性能。咦?是不是很奇怪?因?yàn)榍懊嬉恢闭f正是因?yàn)橐肓藘?nèi)核緩沖區(qū)(中間緩沖區(qū)),使得數(shù)據(jù)來回地拷貝,降低了效率。

那先來看看,它為什么說內(nèi)核緩沖區(qū)提高了性能。

對于讀操作而言,內(nèi)核緩沖區(qū)就相當(dāng)于一個“readahead cache”,當(dāng)用戶程序一次只需要讀一小部分?jǐn)?shù)據(jù)時,首先操作系統(tǒng)從磁盤上讀一大塊數(shù)據(jù)到內(nèi)核緩沖區(qū),用戶程序只取走了一小部分( 我可以只 new 了一個 128B的byte數(shù)組啊! new byte[128])。當(dāng)用戶程序下一次再讀數(shù)據(jù),就可以直接從內(nèi)核緩沖區(qū)中取了,操作系統(tǒng)就不需要再次訪問磁盤啦!因?yàn)橛脩粢x的數(shù)據(jù)已經(jīng)在內(nèi)核緩沖區(qū)啦!這也是前面提到的:為什么后續(xù)的讀操作(read()方法調(diào)用)要明顯地比第一次快的原因。從這個角度而言,內(nèi)核緩沖區(qū)確實(shí)提高了讀操作的性能。

再來看寫操作:可以做到 “異步寫”(write asynchronously)。也即:wirte(dest[]) 時,用戶程序告訴操作系統(tǒng),把dest[]數(shù)組中的內(nèi)容寫到XX文件中去,于是write方法就返回了。操作系統(tǒng)則在后臺默默地把用戶緩沖區(qū)中的內(nèi)容(dest[])拷貝到內(nèi)核緩沖區(qū),再把內(nèi)核緩沖區(qū)中的數(shù)據(jù)寫入磁盤。那么,只要內(nèi)核緩沖區(qū)未滿,用戶的write操作就可以很快地返回。這應(yīng)該就是異步刷盤策略吧。

(其實(shí),到這里。以前一個糾結(jié)的問題就是同步IO,異步IO,阻塞IO,非阻塞IO之間的區(qū)別已經(jīng)沒有太大的意義了。這些概念,只是針對的看問題的角度不一樣而已。阻塞、非阻塞是針對線程自身而言;同步、異步是針對線程以及影響它的外部事件而言….)【更加完美、精辟的解釋可以參考這個系列的文章:系統(tǒng)間通信(3)——IO通信模型和JAVA實(shí)踐 上篇】

既然,你把內(nèi)核緩沖區(qū)說得這么強(qiáng)大和完美,那還要 zerocopy干嘛啊???

Unfortunately, this approach itself can become a performance bottleneck if the size of the data requested ?
is considerably larger than the kernel buffer size. The data gets copied multiple times among the disk, kernel buffer, ?
and user buffer before it is finally delivered to the application.
Zero copy improves performance by eliminating these redundant data copies.
終于輪到zerocopy粉墨登場了。當(dāng)需要傳輸?shù)臄?shù)據(jù)遠(yuǎn)遠(yuǎn)大于內(nèi)核緩沖區(qū)的大小時,內(nèi)核緩沖區(qū)就會成為瓶頸。這也是為什么zerocopy技術(shù)合適大文件傳輸?shù)脑?。?nèi)核緩沖區(qū)為啥成為了瓶頸?—我想,很大的一個原因是它已經(jīng)起不到“緩沖”的功能了,畢竟傳輸?shù)臄?shù)據(jù)量太大了。

下面來看看zerocopy技術(shù)是如何來處理文件傳輸?shù)摹?/p>

當(dāng) transferTo()方法 被調(diào)用時,由用戶態(tài)切換到內(nèi)核態(tài)。完成的動作是:DMA將數(shù)據(jù)從磁盤讀入 Read buffer中(第一次數(shù)據(jù)拷貝)。然后,還是在內(nèi)核空間中,將數(shù)據(jù)從Read buffer 拷貝到 Socket buffer(第二次數(shù)據(jù)拷貝),最終再將數(shù)據(jù)從 Socket buffer 拷貝到 NIC buffer(第三次數(shù)據(jù)拷貝)。然后,再從內(nèi)核態(tài)返回到用戶態(tài)。

上面整個過程就只涉及到了:三次數(shù)據(jù)拷貝和二次上下文切換。感覺也才減少了一次數(shù)據(jù)拷貝嘛。但這里已經(jīng)不涉及用戶空間的緩沖區(qū)了。

三次數(shù)據(jù)拷貝中,也只有一次拷貝需要到CPU的干預(yù)。(第2次拷貝),而前面的傳統(tǒng)數(shù)據(jù)拷貝需要四次且有三次拷貝需要CPU的干預(yù)。

This is an improvement: we’ve reduced the number of context switches from four to two and reduced the number of data copies
from four to three (only one of which involves the CPU)

如果說zerocopy技術(shù)只能完成到這步,那也就 just so so 了。

We can further reduce the data duplication done by the kernel if the underlying network interface card supports ?
gather operations. In Linux kernels 2.4 and later, the socket buffer descriptor was modified to accommodate this requirement. ?
This approach not only reduces multiple context switches but also eliminates the duplicated data copies that ?
require CPU involvement. ?
也就是說,如果底層的網(wǎng)絡(luò)硬件以及操作系統(tǒng)支持,還可以進(jìn)一步減少數(shù)據(jù)拷貝次數(shù) 以及 CPU干預(yù)次數(shù)。

這里一共只有兩次拷貝 和 兩次上下文切換。而且這兩次拷貝都是DMA copy,并不需要CPU干預(yù)(嚴(yán)謹(jǐn)一點(diǎn)的話就是不完全需要吧.)。

整個過程如下:

用戶程序執(zhí)行 transferTo()方法,導(dǎo)致一次系統(tǒng)調(diào)用,從用戶態(tài)切換到內(nèi)核態(tài)。完成的動作是:DMA將數(shù)據(jù)從磁盤中拷貝到Read buffer

用一個描述符標(biāo)記此次待傳輸數(shù)據(jù)的地址以及長度,DMA直接把數(shù)據(jù)從Read buffer 傳輸?shù)?NIC buffer。數(shù)據(jù)拷貝過程都不用CPU干預(yù)了。

相關(guān)推薦:

小結(jié)Node.js中非阻塞IO和事件循環(huán)_node.js

Node.js 的異步?IO?性能探討_node.js

以上就是JAVA IO 以及 NIO 理解的詳細(xì)內(nèi)容,更多請關(guān)注php中文網(wǎng)其它相關(guān)文章!

java速學(xué)教程(入門到精通)
java速學(xué)教程(入門到精通)

java怎么學(xué)習(xí)?java怎么入門?java在哪學(xué)?java怎么學(xué)才快?不用擔(dān)心,這里為大家提供了java速學(xué)教程(入門到精通),有需要的小伙伴保存下載就能學(xué)習(xí)啦!

下載
本文內(nèi)容由網(wǎng)友自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,本站不承擔(dān)相應(yīng)法律責(zé)任。如您發(fā)現(xiàn)有涉嫌抄襲侵權(quán)的內(nèi)容,請聯(lián)系admin@php.cn
最新問題
開源免費(fèi)商場系統(tǒng)廣告
最新下載
更多>
網(wǎng)站特效
網(wǎng)站源碼
網(wǎng)站素材
前端模板
關(guān)于我們 免責(zé)申明 意見反饋 講師合作 廣告合作 最新更新
php中文網(wǎng):公益在線php培訓(xùn),幫助PHP學(xué)習(xí)者快速成長!
關(guān)注服務(wù)號 技術(shù)交流群
PHP中文網(wǎng)訂閱號
每天精選資源文章推送
PHP中文網(wǎng)APP
隨時隨地碎片化學(xué)習(xí)
PHP中文網(wǎng)抖音號
發(fā)現(xiàn)有趣的

Copyright 2014-2025 http://www.miracleart.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號