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


  1. 底層發起JNI調用,創建堆外緩沖區;
  2. JNI中發起read系統調用,此時需要由用戶空間切換到內核空間;
  3. 進入到內核空間,DMA讀取文件數據到內核緩沖區(DMA拷貝);
  4. 將內核緩沖區的數據拷貝到用戶緩沖區(CPU拷貝),切換回用戶空間;
  5. 將堆外緩沖區的數據拷貝到JVM堆內緩沖區中(CPU拷貝);

【Java】Java中的零拷貝

文章插圖
在Java的NIO中,提供了DirectByteBuffer , 可以直接分配堆外內存,減少了一次從堆外內存到堆內內存的復制(CPU復制):
【Java】Java中的零拷貝

文章插圖
直接I/O緩存I/O經過了Page Cache,讀取過程中需要將數據從Page Cache的緩沖區中拷貝到用戶空間的緩存區,那么有沒有一種方式可以省去這個拷貝的過程?
答案是有的,那就是直接I/O,應用程序直接訪問磁盤數據 , 繞過了Page Cache,省去了從內核緩沖區拷貝到用戶緩沖區的過程:
【Java】Java中的零拷貝

文章插圖
目前JAVA并沒有原生的直接/O操作方式,不過公眾號博主Kirito提供了在JAVA中進行直接I/O操作的方法,具體參見【Kirito的技術分享】Java 文件 IO 操作之 DirectIO 。
內存映射內存映射就是將虛擬空間地址映射到物理空間地址 , 每個進程維護了一張頁表 , 記錄虛擬地址和物理地址之間的映射關系 , 當進程訪問的虛擬地址在頁表中無法查到映射關系時 , 系統產生缺頁異常,進入內核空間為虛擬地址分配物理內存,并更新頁表 , 記錄映射關系 。
文件映射
內存映射除了映射虛擬空間地址和物理空間地址,還包括將磁盤的文件內容映射到虛擬地址空間,稱為文件映射,此時可以通過訪問內存來訪問文件里面的數據。
mmap系統調用可以將文件映射到虛擬內存空間 。文件映射的流程如下:
  1. 進行mmap系統調用,將文件和虛擬地址空間建立映射,注意此時還沒有分配物理內存空間 , 只是在邏輯上建立了虛擬地址和文件之間的映射關系,物理內存只有真正使用的時候才會分配 。
  2. 應用程序訪問用戶空間虛擬內存中的某個地址,發現無法在頁表中查到數據 , 產生缺頁異常 , 此時進入內核空間
  3. 因為不能直接使用物理地址,所以需要使用內核的虛擬地址臨時建立與物理內存的映射關系,將文件內容讀取到物理內存中,待數據讀取完畢之后取消臨時映射即可 。
  4. 缺頁異常處理完畢,物理內存中已經加載了文件的數據,此時用戶空間就可以通過虛擬地址直接訪問物理內存中映射的文件數據 。

【Java】Java中的零拷貝

文章插圖
從文件映射的流程中可以看出它與緩存I/O相比 , 少了從內核緩沖區將數據拷貝到用戶緩沖區的步驟,減少了一次拷貝 。
Java NIO中提供了MappedByteBuffer來處理文件映射,下面是一個讀取文件的例子:
public class MappedByteBufferTest {public static void main(String[] args) {try (RandomAccessFile file = new RandomAccessFile(new File("/Users/sml/test.txt"), "r")) {// 獲取FileChannelFileChannel fileChannel = file.getChannel();long size = fileChannel.size();// 調用map方法進行文件映射,返回MappedByteBufferMappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);byte[] bytes = new byte[(int)size];for (int i = 0; i < size; i++) {// 讀取數據bytes[i] = mappedByteBuffer.get();}} catch (Exception e) {e.printStackTrace();}}}零拷貝零拷貝一般指的是從磁盤讀取文件發送到網絡或者從網絡接收數據寫入到磁盤文件的過程中,減少數據的拷貝次數 。
網絡I/O
網絡I/O與網絡數據發送/接收有關,與文件I/O的底層原理一致,同樣以讀取數據為例,文件I/O是從磁盤讀取文件,網絡I/O是從網卡中讀取數據 。比如客戶端與服務端建立了一個連接,客戶端向服務端發送數據 , 服務端從網卡中讀取客戶端發送的數據到內核中的socket緩沖區,再將socket緩沖區的數據復制到用戶空間的緩沖區:
【Java】Java中的零拷貝

文章插圖

推薦閱讀