因为windows和Linux、Unix不同,下面所说的零拷贝以及内核空间切换方式都是基于linux和unix分析的:
IO操作:
但凡涉及到IO操作的,底层基本上都是读和写的过程;
因此接下来将会基于读和写来分析;
user space:用户空间
kemel space:内核空间
hardware:硬盘
读:我们要从磁盘中将数据读取到内存里,首先java虚拟机会发出一个read() syscall (都是native方法)的系统调用,也就是说通过用户空间来系统调用read() syscall发送给内核空间,这个时候就会出现空间模式切换,从用户空间模式切换成内核空间模式,然后内核空间就会想磁盘发出ask for data的读取数据的请求,然后数据就会读取到“data to kemel buffer though DMA”(内核缓存,DMA-direct memory access)当中,然后又将内核缓存区中的内容原封不动的拷贝到用户缓存区当中(copy data to user buffer),然后执行业务逻辑(code logic continues);
写:我们要将数据写到磁盘中,首先java虚拟机会发出一个write() syscall的系统调用,并且将用户空间数据拷贝到内核空间缓存区当中(读的内核缓存区和写的内核缓存区,他们两个是不一样的),然后在从内核空间缓存区写入到磁盘中。
总结:用户空间其实没有对数据做任何的处理,只是一个中转站而已(都是内核空间对磁盘进行读和写操作)。用户空间的缓存区就是一个临时存放数据的载体而已。而且在用户空间模式和内核空间模式切换的过程中有较大的内存消耗。相对于一些并发量高的场景下,非常不适用。
NIO操作:
IO操作的时候,用户空间向内核空间发送的是一个read() syscall的系统调用,而NIO操作的是,用户空间向内核空间发送的是一个 sendfile() syscall的系统调用。我们可以看到当用户空间向内核空间发送请求之后,所以关于读和写的操作全部在内核空间中进行,用户空间从开始到结束一共也就执行了两次,而之前NIO操作中,它从开始到结束一共执行了四次。这就是一个很显著的提高。这样就实现了操作系统上的零拷贝。
问题来了,因为内核空间与硬件之间的处理对于用户来说是透明的,也就是黑盒操作,在这种情况下,如果我们想操作数据的话,怎么办呢?
这个时候内存映射字节缓存(nio中MapByteBuffer)就出现了。也就是说数据本身是在内核缓存区当中的,当时通过内存映射字节缓存,就可以直接在用户空间进行处理。