上图描述的是不使用零拷贝,从磁盘读数据然后进行一个循环之后再写到磁盘或者网络的操作
首先我们JVM所在的用户空间(User Space)调用一个读的native方法,它肯定是操作了系统的某些api,向内核控件发送了一个请求,然后就会进行上下文切换到内核空间(Kemel Space) ,然后内核空间就会把我们存在网络上或者说是硬盘里的数据通过一种叫Direct Memory Access读到内核空间的缓冲区(这里是第一次拷贝),此时注意到,我们的用户空间是无法直接访问内核空间的,所以要把内核空间的缓存原封不动的拷贝到,用户空间的缓存中。(这是第二次拷贝)。然后我们的的read操作就结束了
这里进行了2次上上下文切换和2次拷贝操作
然后我们开始写数据(write),我们要先把直接从内核空间拷贝到用户空间的数据,又拷贝到内核空间,然后再把数据写到磁盘或者网络。
我们可以看出它一共进行了2次上下文的切换(内核空间到用户空间的切换),和两次不必要的拷贝(从内核空间到用户空间再从用户空间到内核空间)。
用户空间在这里就是一个中转站的存在,没有对数据进行任何的修改。这是我们传统的inputstream和outputstream对于底层的反映,在数据量不大的时候可能没什么感觉,如果我们追求的是极致的艺术那就不行了,太弟弟了!
下面这张图是改进后的 的示意图
Java NIO Zero Copy是完全依靠操作系统的,这不是JVM的操作JVM只是调用了这个操作。好在现在大部分的操作系统都实现了这种操作的api。
首先我们的JVM所在的用户空间还是向之前一样向内核空间发送一个请求,然后内核空间请求硬盘上的数据把数据读到内核空间的缓存,然后再把数据拷贝到我们socket的缓存,最后把数据写出去。从这里看我们的操作相比之前已经有很大的优化了。
这是最好的版本
这里我们可以看出他少了一个把数据拷贝到我们socket的缓存的操作,这里其实是需要操作系统底层的支持的,要需要一种文件描述符的一种操作,标识了文件内存空间啊,长度,偏移量等数据。到时候直接传递这个文件描述符就可以了,其实就像传了一个指针一样,就可以实现这种操作。
但这里又存在一种问题,JVM无法直接操作数据了。这时候MappedXXBuffer出场了,它可以把内核空间的数据映射到我们的jvm中,这里就不详细展开了。
以上仅为博主进行了一些列学习活动之后的个人总结