【Java】Java中的零拷贝

物理内存
计算机物理内存条的容量 , 比如我们买电脑会关注内存大小有多少G , 这个容量就是计算机的物理内存 。
【【Java】Java中的零拷贝】虚拟内存
操作系统为每个进程分配了独立的虚拟地址空间,也就是虚拟内存,虚拟地址空间又分为用户空间和内核空间,操作系统的位数不同 , 虚拟地址空间的大小也不同 , 32位操作系统虚拟地址内核空间为1G , 用户空间大小为3G , 64位操作系统用户空间和内核空间大小各为128T:

【Java】Java中的零拷贝

文章插图
既然每个进程都拥有一块独立的虚拟地址空间,那么所有进程的虚拟地址空间大小加起来必定大于物理内存的大?。?所以虚拟地址空间只是一个虚拟的概念,只有需要分配内存的时候才会为虚拟内存分配物理内存 , 并通过内存映射来管理虚拟地址和物理内存地址之间的映射关系 。
用户空间 / 内核空间
用户空间:是运行用户程序代码的地方,为了保证系统内核的安全,它不能直接访问内存等硬件设备,必须通过系统调用进入到内核空间来访问那些受限的资源 。
内核空间:是运行内核代码的地方 , 可以执行任意的指令访问系统资源 , 既可以访问内核空间也可以访问用户空间 。
用户态:进程运行在用户空间时处于用户态 。
内核态:进程运行在内核空间时处于内核态 。
文件I/O文件I/O与读写文件有关,比如我们启动了一个程序,此时运行在用户空间(用户态),接着准备做一个读取磁盘文件的操作,由于用户空间是无法直接从磁盘读取文件的,所以需要调用内核提供的接口来完成文件的读?。?调用内核的接口的过程中由用户空间进入到了内核空间(内核态),DMA从磁盘读取文件到内核的缓冲区 , 之后再将数据从内核的缓冲区拷贝到用户空间完成文件的读取操作:
【Java】Java中的零拷贝

文章插图
  1. 应用程序调用read函数发起系统调用,此时由用户空间切换到内核空间;
  2. 内核通过DMA从磁盘拷贝数据到内核缓冲区(DMA复制);
  3. 将内核缓冲区的数据拷贝到用户空间的缓冲区(CPU复制),切换回用户空间;
可以发现,整个读取过程发生了两次数据拷贝,一次是DMA将磁盘上的文件数据拷贝到内核缓冲区,一次是将内核缓冲区的数据拷贝到用户缓冲区 。写操作与读取操作类似 , 只不过是将用户缓冲区的数据拷贝到内核缓冲区 , 再将内核缓冲区的数据拷贝到文件 。
文件I/O从操作系统的角度来看还可以划分为缓存I/O、直接I/O和mmap内存映射 。
缓存I/O也称标准I/O,上面提到的文件I/O读取数据的例子就是使用的缓存I/O , 它需要将数据先拷贝到内核缓冲区 , 再将内核缓冲区的数据拷贝到用户缓冲区 , 数据经过两次拷贝,内核缓冲区和用户缓冲区分别指向不同的物理内存 , 在文件I/O中 , 内核缓冲区是在Page Cache层 , 这也是称为缓存I/O的原因:
【Java】Java中的零拷贝

文章插图
JAVA中通过java.io包下进行读写文件使用的就是缓存I/O 。
为什么需要缓存IO?
因为磁盘I/O是比较耗时的操作 , 如果每次都从磁盘上读取文件 , 性能将会大大下降,为了提升读取性能,增加了一层Page Cache,用于缓存读取的文件数据,Page Cache占用的是内存,从内存读取的速度远远大于从磁盘读?。诤嘶撼迩褪窃赑age Cache中开辟的一块内存,用户空间进行系统调用读取文件内容时,首先会判断Page Cache中是否缓存了文件的内容,如果缓存了直接读取即可,否则再从磁盘读?。曰捍鍵/O可以减少磁盘I/O的次数提升性能 。
文件的写操作同样如此 , 进行写操作时,将数据先写到Page Cache的缓冲区中,后续由操作系统将数据刷回到磁盘中 。
缓存I/O的优缺点
优点:减少磁盘I/O次数,提升读写性能 。
缺点:数据需要在内核空间和用户空间来回拷贝 。
DirectByteBuffer使用缓存I/O读取数据时,数据会经过两次拷贝 , 经过两次拷贝是从系统调用开始讲起,在JAVA中由于涉及到JVM堆内和堆外内存,如果使用java.io下的类进行文件读写实际上还会再多一次拷贝(详细可参考【JAVA】普通IO数据拷贝次数的问题探讨):

推荐阅读