文件传输零拷贝的Java实现

零拷贝的概念介绍

“Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.
零拷贝表示:在计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。

操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

用户空间:普通应用程序使用的虚拟内存空间称为用户空间,用户空间中的代码运行在较低的特权级别上,只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,也不能直接访问内核空间和硬件设备,以及其他一些具体的使用限制。

内核空间:一部分为核心软件,即是kernel,也称作内核空间,Linux 操作系统和驱动程序运行在内核空间。

DMA(Direct Memory Access):是一种不经过CPU而直接从内存存取数据的数据交换模式。在DMA模式下,CPU只须向DMA控制器下达指令,让DMA控制器来处理数据的传送,数据传送完毕再把信息反馈给CPU,这样就很大程度上减轻了CPU资源占有率,可以大大节省系统资源。

零拷贝技术

普通文件一次读写涉及到四次数据文件的拷贝、四次用户模式和内核模式之间的上下文切换。
第一次复制:DMA从磁盘读取文件内容存储在内核空间的缓冲区
第二次复制:数据从内核空间缓冲区复制到用户空间缓冲区
第三次复制:数据从用户空间缓冲区复制到socket特定缓冲区(内核缓冲区)
第四次复制:数据从内核缓冲区传递至协议引擎

在linux 2.4及以上版本的内核中(如linux 6或centos 6以上的版本),开发者修改了socket buffer descriptor,使网卡支持 gather operation,通过kernel进一步减少数据的拷贝操作。这个方法不仅减少了上下文切换,还消除了和CPU有关的数据拷贝。

使用零拷贝技术后,文件读写只需要两次拷贝。把普通文件的读写的第二次和第三次拷贝省去。
第一次复制:DMA从磁盘读取文件内容存储在内核空间的缓冲区
第二次复制:数据从内核缓冲区传递至协议引擎

Zero Copy的模式中,避免了数据在用户空间和内核空间之间的拷贝,避免消耗CPU周期和内存带宽,从而提高了系统的整体性能。Linux中的sendfile()以及Java NIO中的FileChannel.transferTo()方法都实现了零拷贝的功能,而在Netty中也通过在FileRegion中包装了NIO的FileChannel.transferTo()方法实现了零拷贝。

零拷贝的使用场景

考虑从文件读取并通过网络将数据传输到另一个程序的情况。(静态内容的Web应用程序,FTP服务器,邮件服务器等等)
Kafka、Netty等都有实际的应用场景。

【引用Netty中的零拷贝】【Netty的零拷贝体现在三个方面: 1. Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。 2. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。 3. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。】

零拷贝的Java代码实现

拷贝1G的文件,在Mac上运行无法体现差距。。

使用零拷贝的实现,Mac上运行时长:22s152ms

1
2
3
4
5
6
7
8
9
10
11
12
public void copyFile(String from, String to) throws IOException {
    FileChannel fromChannel = new RandomAccessFile(from, "rw").getChannel();
    FileChannel toChannel = new RandomAccessFile(to, "rw").getChannel();

    long position = 0;
    long count = fromChannel.size();

    fromChannel.transferTo(position, count, toChannel);

    fromChannel.close();
    toChannel.close();
}

正常文件拷贝的实现,Mac上运行时长:23s548ms

1
2
3
4
5
6
7
8
9
10
11
12
13
public void copyFile(String from, String to) throws IOException {
    FileInputStream input = new FileInputStream(from);
    FileOutputStream output = new FileOutputStream(to);

    byte[] b = new byte[1024];
    int n = 0;
    while ((n = input.read(b)) != -1) {
        output.write(b, 0, n);
    }

    input.close();
    output.close();
}

参考资料

JAVA Zero Copy的相关知识

Java NIO 文件IO-内存映射文件MappedByteBuffer与zerocopy

Netty中的零拷贝

kafka通过零拷贝实现高效的数据传输

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值