前言:
而今姐妹们对“免费空间java”大体比较注重,大家都想要知道一些“免费空间java”的相关文章。那么小编同时在网络上汇集了一些关于“免费空间java””的相关资讯,希望同学们能喜欢,看官们一起来了解一下吧!在开始讲解的过程中我们首先提出几个问题
1.什么是零拷贝?
2.用零拷贝的好处,为什么要用零拷贝?
3.在Java中那些步骤或者地方用了零拷贝?
4.Java中支持那些零拷贝?
5.零拷贝是怎么实现的?
带着问题我们去寻找答案
1.什么是零拷贝?
文档是这样描述的
"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.
从定义看出,零拷贝指的是计算机在操作的时候,CPU不需要为数据在内存中相互拷贝而浪费资源。
直接是通过计算机在网络上发送文件时,不需要将文件内容从用用户空间拷贝到内核空间,而是直接在内核空间中传输到网络的方式。
2.用零拷贝的好处?
1.提搞了资源的利用率。减少不必要的cpu的资源消耗,省去了从用户空间到内核空间之间的内存的复制产生的资源消耗。
2.提高了效率。减少了用户空间和内核空间之间上下文之间的切换产生的时间
3.提搞了内存带宽的占用。
3.在Java中那些步骤或者地方用了零拷贝?
Kafka中的零拷贝
Kafka两个重要过程都使用了零拷贝技术,且都是操作系统层面的狭义零拷贝,一是Producer生产的数据存到broker,二是 Consumer从broker读取数据。
Producer生产的数据持久化到broker,采用mmap文件映射,实现顺序的快速写入;
Customer从broker读取数据,采用sendfile,将磁盘文件读到OS内核缓冲区后,直接转到socket buffer进行网络发送
Netty中的零拷贝
Netty中的Zero-copy与上面我们所提到的OS层面上的Zero-copy不太一样, Netty的Zero-copy完全是在用户态(Java层面)的,它的Zero-copy的更多的是偏向于优化数据操作这样的概念。
Netty的Zero-copy体现在如下几个个方面
Netty提供了CompositeByteBuf类,它可以将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝。
通过wrap操作,我们可以将byte[]数组、ByteBuf、 ByteBuffer 等包装成一个 Netty ByteBuf对象,进而避免了拷贝操作。
ByteBuf支持slice 操作,因此可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf,避免了内存的拷贝。
通过FileRegion包装的FileChannel.tranferTo实现文件传输,可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
4.Java中支持那些零拷贝?
1.sendFile()系统调用
2.mmap()系统调用
mmap和sendFile的区别:
1.mmap适合小数据量读写 sendFile适合大文件传输
2.mmap支持传输的时候修改数据,sendFile不支持
3. mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝
4.sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)。
5.零拷贝是怎么实现的?
零拷贝的实现是不依赖Java本身,主要是由操作系统决定的,操作系统支持了零拷贝就有,不支持实现就没有。
传统IO方式 (IO传输效率低)
在Java中,我们首先来分析在没有使用零拷贝的情况下,传统IO是怎么通过一个inputSteam从源数据中读取数据流到一个缓冲区里,然后再将他们输入到outputStream里,现实的场景就是从某台机器将一份数据(比如一个文件)通过网络传输到另外一台机器,按照一般的思路,我们用Java语言来描述发送端的逻辑大致如下:
// 首先建立一个Socket的连接Socket socket = new Socket(HOST, PORT);// 把文件读取到输入流中InputStream inputStream = new FileInputStream(FILE_PATH);// 创建一个输出流OutputStream outputStream = new DataOutputStream(socket.getOutputStream());// 将输入流中的文件转换到输出流中以便输出byte[] buffer = new byte[4096];while (inputStream.read(buffer) >= 0) {outputStream.write(buffer);}// 关闭输出和输入流outputStream.close();socket.close();inputStream.close();
看起来简单实现简单,但是深入到操作系统的话,就会发现实际的微观操作会更复杂,具体的话看以下时序图描述
具体的步骤如下:JVM为Java虚拟机 OS为操作系统
1.JVM向OS发出read()系统调用,触发上下切换,从用户态切换到内核态
2.内核态从硬盘读取文件内容,通过直接内存访问(DMA)存入到内核地址空间的缓冲区中
3.将数据从内核缓冲区拷贝到用户空间的缓冲区,read()系统调用返回,并从内核态切换回用户态
4.JVM向OS发出write()系统调用,触发上下文切换,从用户态切换到内核态
5.将数据从用户态缓冲区的数据拷贝到内核态中与Socket关联的缓冲区中
6.数据最终经由Socket通过DMA传输到硬件缓冲区(例如:网卡),write()系统调用返回,并且重新由内核态切换成用户态
整个过程一共发生了4次上下文的切换和4次拷贝,我们都知道上下文的切换是CPU密集型的工作,而数据拷贝是I/O密集型的工作,如果一次简单的传输就要像上面这样复杂的话,效率是相当低下的。而零拷贝就能解决整个问题,通过减少上下文的切换和数据拷贝提高效率。
零拷贝的数据传输方法
通过上面的分析可以看出,第二,三次的拷贝是没有意义的,就是从用户空间到内核空间的来回复制,数据应该直接从内核缓冲区送入到Socket缓冲区。零拷贝机制就是实现了这一点,不过零拷贝的实现需要由操作系统直接支持,不同的操作系统有不同的实现方法。大多数的Unix系统都是提供一个名为sendFile()方法来进行系统的调用.
文档是这样描述的
sendfile() copies data between one file descriptor and another.
Because this copying is done within the kernel, sendfile() is more efficient than the combination of read(2) and write(2), which would require transferring data to and from user space.
意思是:
sendfile()描述的是一个文件和另一个文件之间数据的复制。
因为这种复制是在内核中完成的,所以sendfile()比read()和write()的组合更有效,后者需要在用户空间之间传输数据。
以下是零拷贝机制下的数据传输时序图:
可见确实是消除了从内核态到用户态之间的数据的传输复制,因此从零拷贝整个词的实际意义上来说,并不是不会发生任何拷贝,站在内核态角度来说,只是减少了用户态和内核态的来回复制。在Java NIO包中提供了零拷贝机制对应的API,即FileChannel.transferTo()方法。而FIileChannel的实现类并不在JDK中,而位于sun.nio.FileChannellmpl类中,零拷贝的具体实现的话自然也是本地的native方法。
Java零拷贝的方法实现代码如下:
// 创建socket通道SocketAddress socketAddress = new InetSocketAddress(HOST, PORT);// 打开socket通道SocketChannel socketChannel = SocketChannel.open();// 进行连接socketChannel.connect(socketAddress);// 本地文件File file = new File(FILE_PATH);FileChannel fileChannel = new FileInputStream(file).getChannel();// 把内核态本地文件复制到内核态中的socket通道的缓冲去区,在通过DMA直接内存访问发送到网卡中fileChannel.transferTo(0, file.length(), socketChannel);// 关闭文件通道fileChannel.close();socketChannel.close();
借助transferTo()方法的话,可见不仅拷贝次数变成了3次,上下文切换的次数也减少到了2次,效率比传统方法提高了很多。但是还不是最完美的状态,下面看一看让它变得更优化的方法。
对Scatter/Gather的支持
在零拷贝的时序图中有一段是“write data to target socket buffer”的回环,也就是从内核态缓冲区再一次拷贝到内核态关联的socket的缓冲区中。这个因为在一般的阻塞DMA中,源物理地址和目标物理地址都必须是连续的,所以只能传输物理上的连续一块数据,每次一个快发起一次中断,知道传输完成,所以必须要在两个缓冲区之间拷贝数据。
Scatter/Gather DMA方式则不同,会预先维护一个物理上不连续的块描述符的链表,描述符中包含有数据的起始地址和长度。传输时只需要遍历链表,按序传输数据,全部完成后发起一次中断即可,效率比Block DMA要高。也就是说,硬件可以通过Scatter/Gather DMA直接从内核缓冲区中取得全部数据,不需要再从内核缓冲区向Socket缓冲区拷贝数据。因此上面的时序图还可以进一步简化。
支持Scatter/Gather的零拷贝时序图
这就是完全体的零拷贝机制了,是不是清爽了很多?相对地,它的流程框图如下。
对内存映射(mmap)的支持
上面讲的机制看起来一切都很好,但是它有一个缺点:如果我想在传输是修改数据本身,就无能为力了,不过很多操作系统也是提供了内存映射也就是mmap,对应的系统调用就是mmap()和munmap().通过它可以将文件数据映射到内核地址空间,直接进行操作,操作完了之后会重新再刷回去,也就是DMA加载磁盘数据到内核态后,用户空间缓冲区(application buffers)和内核缓冲区(kernel buffer)进行映射,数据在用户态和内核态的改变就能刷回去。对应的时序图如下:
当然,天下没有免费的午餐,上面的过程仍然会发生4次上下文切换,3次拷贝,另外,它需要在快表(TLB)中始终维护着所有数据对应的地址空间,直到刷写完成,因此处理缺页的overhead也会更大。在使用该机制时,需要权衡效率。NIO框架中提供了MappedByteBuffer用来支持mmap。它与常用的DirectByteBuffer一样,都是在堆外内存分配空间。相对地,HeapByteBuffer在堆内内存分配空间。
标签: #免费空间java