传统的IO操作流程
这里好多人说有4次上下文的切换,这个我感觉不是很重要,次数主要是看开始的状态和最后结束的状态,上图中程序在数据到网络时就结束了,其实这之后还有一次内核到进程的切换,开始和结束都是在进程状态
NIO实现零拷贝方式一transferTo()
这个流程涉及到3次数据的拷贝 内核态与用户态上下文的切换次数比传统的模式少了2次
直接在内核态完成数据的传输 数据不进入进程 在操作系统的角度来看,数据只是在自己这里传输没有拷贝到进程内所以叫做零拷贝.
NIO 中的 FileChannel 对象有一个
transferTo(long position, long count,WritableByteChannel target) 利用这个方法实现零拷贝
这个方法是依赖底层的操作系统 linux sendFile()方法来实现的
这种方式适用场景 : 大文件的传输 且 文件不需要修改直接传递
NIO实现零拷贝方式二 mmap 内存映射
3次数据的拷贝 用户态到内核态的转化跟传统io没有区别 只是少了一次数据的拷贝
直接内存(mmap技术)将文件直接映射到内核空间的内存,返回一个操作地址(address),它解决了文件数据需要拷贝到JVM才能进行操作的窘境。而是直接在内核空间直接进行操作,省去了内核空间拷贝到用户空间这一步操作。
NIO中用来做内存映射的对象
HeapByteBuffer
在调用ByteBuffer.allocate()时使用。 它被称为堆,因为它保存在JVM的堆空间中,因此你可以获得所有优势,如GC支持和缓存优化。 但是,它不是页面对齐的,这意味着如果你需要通过JNI与本地代码交谈,JVM将不得不复制到对齐的缓冲区空间。
DirectByteBuffer
在调用ByteBuffer.allocateDirect()时使用。 JVM将使用malloc()在堆空间之外分配内存空间。 因为它不是由JVM管理的,所以你的内存空间是页面对齐的,不受GC影响,这使得它成为处理本地代码的完美选择。 然而,你要C程序员一样,自己管理这个内存,必须自己分配和释放内存来防止内存泄漏。
MappedByteBuffer
在调用FileChannel.map()时使用。 与DirectByteBuffer类似,这也是JVM堆外部的情况。 它基本上作为OS mmap()系统调用的包装函数,以便代码直接操作映射的物理内存数据。