java 零拷贝

Java零拷贝机制解析

Linux提供的领拷贝技术 Java并不是全支持,支持2种(内存映射mmap、sendfile);

NIO提供的内存映射 MappedByteBuffer

  • 首先要说明的是,JavaNlO中 的Channel (通道)就相当于操作系统中的内核缓冲区,有可能是读缓冲区,也有可能是网络缓冲区,而Buffer就相当于操作系统中的用户缓冲区。

 

MappedByteBuffer mappedByteBuffer = new RandomAccessFile(file, "r") 
                                 .getChannel() 
                                .map(FileChannel.MapMode.READ_ONLY, 0, len);

底层就是调用Linux mmap()实现的。

NIO中的FileChannel.map()方法其实就是采用了操作系统中的内存映射方式,底层就是调用Linux mmap()实现的。

将内核缓冲区的内存和用户缓冲区的内存做了一个地址映射。这种方式适合读取大文件,同时也能对文件内容进行更改,但是如果其后要通过SocketChannel发送,还是需要CPU进行数据的拷贝。
使用MappedByteBuffer,小文件,效率不高;一个进程访问,效率也不高。

MappedByteBuffer只能通过调用FileChannel的map()取得,再没有其他方式。
FileChannel.map()是抽象方法,具体实现是在 FileChannelImpl.c 可自行查看JDK源码,其map0()方法就是调用了Linux内核的mmap的API。
使用 MappedByteBuffer类要注意的是:mmap的文件映射,在full gc时才会进行释放。当close时,需要手动清除内存映射文件,可以反射调用sun.misc.Cleaner方法。

NIO提供的sendfile

  • FileChannel.transferTo()方法直接将当前通道内容传输到另一个通道,没有涉及到Buffer的任何操作,NIO中 的Buffer是JVM堆或者堆外内存,但不论如何他们都是操作系统内核空间的内存
  • transferTo()的实现方式就是通过系统调用sendfile() (当然这是Linux中的系统调用)

 

//使用sendfile:读取磁盘文件,并网络发送
FileChannel sourceChannel = new RandomAccessFile(source, "rw").getChannel();
SocketChannel socketChannel = SocketChannel.open(sa);
sourceChannel.transferTo(0, sourceChannel.size(), socketChannel);

ZeroCopyFile实现文件复制

 

class ZeroCopyFile {

    public void copyFile(File src, File dest) {
        try (FileChannel srcChannel = new FileInputStream(src).getChannel();
             FileChannel destChannel = new FileInputStream(dest).getChannel()) {

            srcChannel.transferTo(0, srcChannel.size(), destChannel);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意: Java NIO提供的FileChannel.transferTo 和 transferFrom 并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供 sendfile 这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。

直接内存方式(堆外内存)

 

我们先不说Java,我们从c说起。从Linux系统角度出发,内存分为俩块

  1. 内核态,由操作系统内核操作,读写磁盘,读写网络都是由这负责
  2. 用户态,我们的c应用程序能访问到的部分

当我们要读文件的时候,首先由内核态负责将数据从磁盘读到内核态里,再从内核态拷贝到我们用户态弄内存里,c程序里操作的也就是这部分用户态的内存。

说完c我们再说说Java,jvm启动的时候会在用户态申请一块内存,申请的这块内存中有一部分会被称为堆,一般我我们申请的对象就会放在这个堆上,堆上的对象是受gc管理的。

那么除了堆内的内存,其他的内存都被称为对外内存。在堆外内存中如果我们是通过Java的directbuffer申请的,那么这块内存其实也是间接受gc管理的,而如果我们通过jni直接调用c函数申请一块堆外内存,那么这块内存就只能我们自己手动管理了。

当我们在Java中发起一个文件读操作会发生什么呢?首先内核会将数据从磁盘读到内存,再从内核拷贝到用户态的堆外内存(这部分是jvm实现),然后再将数据从堆外拷贝到堆内。拷贝到堆内其实就是我们在Java中自己手动申请的byte数组中。

以上是Java传统io的方式,我们发现经过了俩次内存拷贝,而nio中只需要使用directbuffer,就不必将数据从堆外拷贝到堆内了,减少了一次内存拷贝,降低了内存的占用,减轻了gc的压力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

life1024

你的鼓励将是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值