java  nio是如何實現零拷貝(zero-copy)的

首先了解關於zero-copy相關的知識點

java  nio是如何實現zero-copy的

上一篇文章中簡單介紹了zero-copy的相關知識,提到了mmap內存直接映射方式,這種方式介於sendfile系統調用與傳統IO之間,其中一個重要原因是sendfile完全在內核空間中完成的,這對於應用程序來說就無法對數據進行操作,也由此,javaNIO是基於mmap內存映射的方式來實現零拷貝的。

實現方式

Java NIO引入了用於通道的緩衝區的ByteBuffer,ByteBuffer有三個主要的實現:

NIO DirectByteBuffer

Java NIO引入了用於通道的緩衝區的ByteBuffer。 ByteBuffer有三個主要的實現:

HeapByteBuffer

在調用ByteBuffer.allocate()時使用。 它被稱爲堆,因爲它保存在JVM的堆空間中,因此您可以獲得所有優勢,如GC支持和緩存優化。 但是,它不是頁面對齊的,這意味着如果您需要通過JNI與本地代碼交談,JVM將不得不復制到對齊的緩衝區空間。

DirectByteBuffer

在調用ByteBuffer.allocateDirect()時使用。 JVM將使用malloc()在堆空間之外分配內存空間。 因爲它不是由JVM管理的,所以你的內存空間是頁面對齊的,不受GC影響,這使得它成爲處理本地代碼的完美選擇。 然而,你要C程序員一樣,自己管理這個內存,必須自己分配和釋放內存來防止內存泄漏。

MappedByteBuffer

在調用FileChannel.map()時使用。 與DirectByteBuffer類似,這也是JVM堆外部的情況。 它基本上作爲OS mmap()系統調用的包裝函數,以便代碼直接操作映射的物理內存數據。

以上解釋聽起來還是雲裏霧裏的,其實

NIO的直接內存是由MappedByteBuffer實現的。核心即是map()方法,該方法把文件映射到內存中,獲得內存地址addr,然後通過這個addr構造MappedByteBuffer類,以暴露各種文件操作API。

由於MappedByteBuffer申請的是堆外內存,因此不受Minor GC控制,只能在發生Full GC時才能被回收。而DirectByteBuffer改善了這一情況,它是MappedByteBuffer類的子類,同時它實現了DirectBuffer接口,維護一個Cleaner對象來完成內存回收。因此它既可以通過Full GC來回收內存,也可以調用clean()方法來進行回收。

對於HeapByteBuffer與MappedByteBuffer的創建方式如下

allocate 方法(創建堆緩衝區)

public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
}

allocate方法創建的緩衝區是創建的HeapByteBuffer實例。

HeapByteBuffer(int cap, int lim) {            // package-private
    super(-1, 0, lim, cap, new byte[cap], 0);
}

從堆緩衝區中看出,所謂堆緩衝區就是在堆內存中創建一個byte[]數組。
allocateDirect創建直接緩衝區

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

我們發現allocate方法創建的緩衝區是創建的DirectByteBuffer實例。
直接緩衝區是通過java中Unsafe類進行在物理內存中創建緩衝區。

與Channel的配合使用
 

package com.expgiga.NIO;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
 
/**
 * 一、Channel:用於源節點與目標節點之間的連接。在Java NIO中,負責緩衝區中數據傳輸,Channel本身不存儲數據,因此需要配合緩衝區進行傳輸。
 *
 * 二、Channel的實現類:
 *     java.nio.channels.Channel 接口:
 *     |-- FileChannel
 *     |-- SocketChannel
 *     |-- ServerSocketChannel
 *     |-- DatagramChannel
 *
 * 三、獲取通道Channel
 * 1.Java針對支持通道的類提供了getChannel()方法
 *   本地IO
 *   FileInputStream/FileOutputStream
 *   RandomAccessFile
 *
 *   網絡IO:
 *   Socket
 *   ServerSocket
 *   DatagramSocket
 *
 * 2.在jdk1.7中的NIO.2針對各個通道提供了靜態方法open()
 *
 * 3.在jdk1.7中的NIO.2的Files工具類的newByteChannel()
 *
 * 四、通道之間的數據傳輸
 * transferFrom()
 * transferTo()
 *
 */
public class TestChannel {
 
    public static void main(String[] args) throws IOException {
 
        /*
         * 1.利用通道完成文件的複製(非直接緩衝區)
         */
        FileInputStream fis = null;
        FileOutputStream fos = null;
 
        FileChannel inChannel = null;
        FileChannel outChannel = null;
 
        try {
            fis = new FileInputStream("1.jpg");
            fos = new FileOutputStream("2.jpg");
            //1.獲取通道
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();
 
            //2.分配指定大小的緩衝區
            ByteBuffer buffer = ByteBuffer.allocate(1024);
 
            //3.將通道中的數據緩衝區中
            while (inChannel.read(buffer) != -1) {
 
                buffer.flip();//切換成都數據模式
 
                //4.將緩衝區中的數據寫入通道中
                outChannel.write(buffer);
                buffer.clear();//清空緩衝區
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
 
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
 
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
 
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
 
 
        /*
         * 2.利用(直接緩衝區)通道完成文件的複製(內存映射文件的方式)
         */
 
        long start = System.currentTimeMillis();
        FileChannel inChannel2 = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        FileChannel outChannel2 = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
 
        //內存映射文件
        MappedByteBuffer inMappedBuf = inChannel2.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel2.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
 
        //直接對緩衝區進行數據讀寫操作
        byte[] dst = new byte[inMappedBuf.limit()];
        inMappedBuf.get(dst);
        outMappedBuf.put(dst);
 
        inChannel2.close();
        outChannel2.close();
 
        long end = System.currentTimeMillis();
        System.out.println("耗費的時間爲:" + (end - start));
 
        /*
         * 通道之間的數據傳輸(直接緩衝區)
         */
        FileChannel inChannel3 = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        FileChannel outChannel3 = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
 
        inChannel3.transferTo(0, inChannel3.size(), outChannel3);
        //等價於
//        outChannel3.transferFrom(inChannel3, 0, inChannel3.size());
 
        inChannel3.close();
        outChannel3.close();
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章