CPU不是瓶颈,I/O操作成为系统瓶颈,
由于操作系统和JVM的I/O模型不匹配,(操作系统基于块,分页,JAVA 的I/O模型是基于流的方式处理,)因此JVM封装的很多高效I/O并不支持
因此在jdk1.4的时候提出了新的I/O模型NIO
NIO提供了新的抽象channel和selector,这套新类提供了强大的框架涵盖了普通系统提供的高效的I/O,
首先看一下操作系统如何作I/O操作的
1:虚拟内存分页
比较早些的时候,PC的内存比较小,比如只有254k,但是一个进程需要使用的内存是20m,那么实际上系统内存就不够用了,因此操作系统提供了虚拟内存机制。那么如何把虚拟内存对应到实际的物理内存呢,这就是内存分页技术。为此虚拟内存和物理内存被分为单位大小相同的页帧,然后为了知道虚拟内存如何映射到物理内存设计了页表(根据虚拟地址查找实际地址),CPU和物理内存之间有一个叫做MMU的组件,用来查找页表,转换为物理内存地址,如果当前不存在一个物理地址对应了入参的虚拟机地址,那么就会向CPU提交一个页错误,然后CPU将控制权交给内核执行页面调度,把数据存储在物理内存中,同时刷新页表和MMU,MMU会返回正确的物理地址给用户进程,对用户基本无感知。
基于虚拟内存,看一下实际上操作系统层面的I/O的情形,注意下面所说的内存都是虚拟内存:
1:缓冲区,
用户发其I/O操作,先在内存中为用户I/O进程开一片缓冲区,然后发送操作系统命令把自己的缓冲区填满,或者清空,内核随即开始查找数据,对内存缓冲区进行高速缓存或者预读取,然后在把内存缓冲区中的数据拷贝到用户的缓冲区中。
不直接拷贝数据的原因是在磁盘上是整块,但是在缓冲区中可能不是连续的,在内核缓冲区可以进行分解整合的操作。
一些增加效率的方法:
发散/汇聚
1:许多操作系统把分解/整合的操作进行的很高校,只需要一次系统调用,就会返回内存中一连串的缓冲区地址给操作系统,操作系统会顺序的填满或者晴空,然后在同步到用户的缓冲区中,这样较少系统调用次数;
虚拟内存:
1: 通过多块虚拟内存地址映射到同一块内存,可以解决设备控制器不能直接存储到用户空间的问题。
2:当寻址空间大与物理内存的时候,采用分页的机制
一个很恰当的比喻就是,你不需要很长的轨道让火车可以从山东开到成都,你只需要足够长的铁轨就ok,采取的方法就是把后面的铁轨迅速扑倒火车前面,只要你的操作足够块,并能满足要求,列车就能像在一条足够长的铁轨上运行,这就是虚拟内存管理需要完成的任务。
具体的文件系统是一个逻辑概念,组织了一组磁盘上不连续的数据。所有的文件I/O都是基于页面调度完成。
执行文件系统I/O全过程,文件系统也有页的概念,通常是内存页的整数倍,从文件系统得知数据具体放在那些文件系统页上,在内核空间分配足够的内存以容纳文件系统页,然后对内存和文件系统页之间做映射,为每个内存产生的页错误,虚拟内存系统捕获页错误,安排页面调入,从硬盘读取页内容,使页有效,一旦页有效之后,文件系统对原始数据进行解析,取的所需的文件内容和文件名可读状态等属性。
内存映射文件,传统文件I/O通过用户进程发布多个read()和write()系统调用来传输数据的,因为文件系统页和用户缓冲区页不是一一对应的关系,所以会发生多次拷贝,从文件系统页到内核缓冲区,在到用户缓冲区,为此操作系统支持了内存映射I/O。