【Java】Java中的零拷貝( 三 )


使用緩存I/O發送數據到網絡
首先看一下使用緩存I/O從磁盤文件讀取數據并發送到網絡上的過程:

【Java】Java中的零拷貝

文章插圖
  1. 用戶發起系統調用,進入到內核態,DMA從磁盤上讀取數據到內核緩沖區(DMA復制);
  2. CPU將內核緩沖區的數據拷貝到用戶緩沖區(CPU復制),切換回到用戶空間;
  3. 再次從用戶空間切換到內核空間,CPU將用戶緩沖區的數據拷貝到socket緩沖區(CPU復制);
  4. DMA將socket緩沖區的數據拷貝到網卡(DMA復制),之后從內核空間切換回用戶空間;
使用緩存I/O數據經過了四次拷貝 , 需要多次在內核空間和用戶空間來回切換 , 影響系統性能 。從數據拷貝的過程可以看到有些步驟其實是多余的,比如第二步,如果可以直接將內核緩存區的數據拷貝到socket緩沖區,或者直接將內核緩沖區的數據拷貝到網卡,豈不是減少了數據拷貝的次數?零拷貝就是這樣一種致力于減少數據拷貝的技術 。
Linux中的零拷貝sendfileLinux在2.1版本中引入了sendfile函數,可以實現將數據從一個文件描述符傳輸到另外一個文件描述符:
  1. 發起sendfile系統調用,進入到內核空間;
  2. DMA從磁盤讀取文件到內核緩沖區(DMA復制);
  3. 將內核緩沖區數據拷貝到socket緩沖區(CPU復制);
  4. 將socket緩沖區數據拷貝到網卡(DMA復制),之后切換回用戶空間;
    【Java】Java中的零拷貝

    文章插圖
sendfile減少了一次數據從內核緩沖區拷貝到用戶緩沖區的過程,可以直接將內核緩沖區的數據拷貝到socket緩沖區 。
sendfile + DMA GATHERLinux在2.4版本中引入了gather技術,我們知道內核緩沖區在內存中有對應的地址,gather操作可以將內核緩沖區的內存地址、地址偏移量信息記錄到socket緩沖區中,之后DMA根據地址信息從內存中讀取數據到網卡中 , 減少了數據從內核緩沖區到socket緩沖區的拷貝過程:
【Java】Java中的零拷貝

文章插圖
可以看到零拷貝并不是指的數據一次拷貝都沒有發生,而是指減少CPU進行數據拷貝的次數 。
Java中的零拷貝MappedByteBuffer在內存映射中說過,可以通過文件映射的方式將磁盤的文件內容映射到虛擬地址空間,用戶空間就可以通過虛擬地址直接訪問物理內存中的映射的文件數據,而Java NIO中也提供了MappedByteBuffer來處理文件映射,使用MappedByteBuffer向網絡中發送數據的過程如下:
  1. 使用MappedByteBuffer建立文件映射 , 用戶空間可以通過虛擬地址直接訪問映射的文件數據;
  2. 將映射的文件數據拷貝到socket網絡緩沖區(CPU復制);
  3. DMA將socket緩沖區的數據拷貝到網卡(DMA復制);

【Java】Java中的零拷貝

文章插圖
MappedByteBuffer減少了從內核緩沖區到用戶緩沖區的數據拷貝 , 可以直接將內核緩沖區的數據拷貝到網絡緩沖區 。
FileChannelJava NIO中的FileChannel可以實現將數據從FileChannel直接傳輸到另一個Channel,它是sendfile的一種實現:
RandomAccessFile file = new RandomAccessFile(new File("/Users/sml/test.txt"), "r");// 獲取FileChannelFileChannel fileChannel = file.getChannel();long size = fileChannel.size();SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8080));fileChannel.transferTo(0,size,socketChannel);參考
【極客時間-倪朋飛】Linux性能優化實戰
【極客時間-劉超】趣談Linux操作系統
【拉勾教育-若地】Netty 核心原理剖析與 RPC 實踐
【 Kirito的技術分享】文件IO操作的最佳實踐
【小碼農叔叔】java使用nio讀寫文件
【占小狼】深入淺出MappedByteBuffer
【零壹技術?!可钊肫饰鯨inux IO原理和幾種零拷貝機制的實現
【tomas家的小撥浪鼓】堆外內存 之 DirectByteBuffer 詳解
網絡IO和磁盤IO詳解

推薦閱讀