Java NIO 机制分析(一) Java IO的演进

1、引言

       Java1.4以前的早期版本,Java对I/O的支持并不完善,开发人员再开发高性能I/O程序的时候,会面临一些巨大的挑战和困难,主要有如下一些问题:java

(1)没有数据缓冲区,I/O性能存在问题正则表达式

(2)没有C或者C++中的Channel概念,只有输入和输出流编程

(3)同步阻塞式I/O通讯(BIO),一般会致使通讯线程被长时间阻塞服务器

(4)支持的字符集有限,硬件可移植性很差网络

本篇从底层的Linux操做系统I/O模型的系统调用开始提及,逐步介绍Java的I/O的发展及相关核心概念。多线程

2、Linux网络I/O模型

    Linux的内核将全部外部设备都看做一个文件来操做,对一个文件的读写操做都会调用内核提供的系统命令,返回一个file descriptor(fd,文件描述符)。而对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。并发

    根据UNIX网络编程对I/O模型的分类,UNIX提供了5种I/O模型,分别以下:异步

(1)阻塞I/O模型:最经常使用的I/O模型就是阻塞I/O模型,缺省情形下,全部文件操做都是缺省的。以套接字接口为例来说解此模型:在进程空间中调用recvfrom,其系统调用直到数据包到达且被复制到应用进程的缓冲区中或者发生错误时才返回,在此期间一直会等待,进程在从调用recvfrom 开始到它返回的整段时间都是被阻塞的,所以被称为阻塞I/O模型。socket

(2)非阻塞I/O模型:recvfrom从应用层到内核的时候,若是该缓冲区没有数据的话,就直接返回一个EWOULDBLOCK错误,通常都对非阻塞I/O模型进行轮询检查这个状态,看内核是否是有数据到来。函数

(3)I/O复用模型:Linux提供select/poll,进程经过将一个或多个fd传递给select或poll系统调用,阻塞在select操做上,这样select/poll能够帮助咱们侦测多个fd是否处于就绪状态。select/poll是顺序扫描fd是否就绪,并且支持的fd数量有限,所以它的使用受到了一些制约。Linux还提供了一个epoll系统调用,epoll使用基于事件驱动方式代替顺序扫描,所以性能高。当有fd就绪时,当即回调函数rollback。

(4)信号驱动I/O模型:首先开启套接口信号驱动I/O功能,并经过系统调用sigaction执行一个信号处理函数(此系统调用当即返回,进程继续工做,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个SIGIO信号,经过信号回调通知应用程序调用recvfrom来读取数据,并通知主循环函数处理数据。

(5)异步I/O:告知内核启动某个操做,并让内核在整个操做完成后(包括将数据从内核复制到用户本身的缓冲区)通知咱们。这种模型与信号驱动模型的主要区别是:信号驱动I/O由内核通知咱们能够开始一个I/O操做;异步I/O模型由内核通知咱们I/O操做合适操做完成。

3、I/O多路复用技术

        从上述咱们能够知道,其实操做系统对于异步IO是支持的,只不过Java在很长的一段时间并无提供异步IO通讯的类库。因为Java NIO的核心类库多路复用器Selector就是基于epoll的多路复用技术实现,下面会重点分析IO多路复用技术。

        在I/O编程过程当中,当须要同时处理多个客户端接入请求时,能够利用多线程或者I/O多路复用技术进行处理。I/O多路复用技术经过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的状况下能够同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O多路复用的最大优点是系统开销小,系统不须要建立新的额外进程或者线程,也不须要维护这些进程和线程的运行,降底了系统的维护工做量,节省了系统资源,I/O多路复用的主要应用场景以下:

(1)服务器须要同时处理多个处于监听状态或者多个链接状态的套接字。

(2)服务器须要同时处理多种网络协议的套接字。

    目前支持I/O多路复用的系统调用有 select,pselect,poll,epoll,在Linux网络编程过程当中,很长一段时间都使用select作轮询和网络事件通知,然而select的一些固有缺陷致使了它的应用受到了很大的限制,最终Linux不得不在新的内核版本中寻找select的替代方案,最终选择了epoll。epoll与select的原理比较相似,为了克服select的缺点,epoll做了不少重大改进,现总结以下:

(1)支持一个进程打开的socket描述符(FD)不受限制(仅受限于操做系统的最大文件句柄数):

        select最大的缺陷就是单个进程所打开的FD是有必定限制的,它由FD_SETSIZE设置,默认值是1024。对于那些须要支持上万个TCP链接的大型服务器来讲显然太少了。能够选择修改这个宏,而后从新编译内核,不过这会带来网络效率的降低。咱们也能够经过选择多进程的方案(传统的Apache方案)解决这个问题,不过虽然在Linux上建立进程的代价比较小,但仍旧是不可忽视的,另外,进程间的数据交换很是麻烦,对于Java因为没有共享内存,须要经过Socket通讯或者其余方式进行数据同步,这带来了额外的性能损耗,增长了程序复杂度,因此也不是一种完美的解决方案。值得庆幸的是,epoll并无这个限制,它所支持的FD上限是操做系统的最大文件句柄数,这个数字远远大于1024。例如,在1GB内存的机器上大约是10万个句柄左右,具体的值能够经过cat/proc/sys/fs/filemax察看,一般状况下这个值跟系统的内存关系比较大。

(2) I/O效率不会随着FD数目的增长而线性降低:

        传统的select/poll另外一个致命弱点就是当你拥有一个很大的socket集合,因为网络延时或者链路空闲,任一时刻只有少部分的socket是“活跃”的,可是select/poll每次调用都会线性扫描所有集合,致使效率呈现线性降低。epoll不存在这个问题,它只会对“活跃”的socket进行操做-这是由于在内核实现中epoll是根据每一个fd上面的callback函数实现的,那么,只有“活跃”的socket才会主动的去调用callback函数,其余idle状态socket则不会。在这点上,epoll实现了一个伪AIO。针对epoll和select性能对比的benchmark测试代表:若是全部的socket都处于活跃态。例如一个高速LAN环境,epoll并不比select/poll效率高太多;相反,若是过多使用epoll_ctl,效率相比还有稍微的降低。可是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

(3)使用mmap加速内核与用户空间的消息传递:

        不管是select,poll仍是epoll都须要内核把FD消息通知给用户空间,如何避免没必要要的内存复制就显得很是重要,epoll是经过内核和用户空间mmap使用同一块内存实现。

(4) epoll的API更加简单:

       用来克服select/poll缺点的方法不仅有epoll,epoll只是一种Linux的实现方案。在freeBSD下有kqueue,而dev/poll是最古老的Solaris的方案,使用难度依次递增。但epoll更加简单。

4、Java IO的演进

      在JDK1.4推出Java NIO以前,基于Java的全部Socket通讯都采用了同步阻塞模式(BIO),这种一请求一应答的通讯模型简化了上层的应用开发,可是在性能和可靠性方面却存在着巨大的瓶颈。所以,在很长一段时间里,大型的应用服务器都采用C或者C++语言开发,由于它们能够直接使用操做系统提供的异步I/O或者AIO能力。当并发访问量增大、响应时间延迟增大以后,采用Java BIO开发的服务端软件只有经过硬件的不断扩容来知足高并发和延时,极大地增长了企业的成本,而且随着集群规模的不断膨胀,系统的可维护性也面临巨大的挑战,只能经过采购性能更高的硬件服务器来解决问题,这会致使恶性循环。

  正是因为Java传统BIO的拙劣表现,才使得Java支持非阻塞I/O的呼声日渐高涨,最终,JDK1.4版本提供了新的NIO类库,Java终于也能够支持非阻塞I/O了。

  JDK1.4推出 NIO 1.0 。新增java.nio包,提供了不少进行异步I/O开发的API和类库,主要的类和接口以下。

  • 进行异步I/O操做的缓冲区ByteBuffer等;
  • 进行异步I/O操做的管道Pipe;
  • 进行各类I/O操做(异步或者同步)的Channel,包括ServerSocketChannel和SocketChannel;
  • 多种字符集的编码能力和解码能力;
  • 实现非阻塞I/O操做的多路复用器selector;
  • 基于流行的Perl实现的正则表达式类库;
  • 文件通道FileChannel。

  新的NIO类库的提供,极大地促进了基于Java的异步非阻塞编程的发展和应用,可是,它依然有不完善的地方,特别是对文件系统的处理能力仍显不足,主要问题以下。

  • 没有统一的文件属性(例如读写权限);
  • API能力比较弱,例如目录的级联建立和递归遍历,每每须要本身实现;
  • 底层存储系统的一些高级API没法使用;
  • 全部的文件操做都是同步阻塞调用,不支持异步文件读写操做。

  JDK1.7推出 NIO 2.0。主要提供了以下三个方面的改进。

  • 提供可以批量获取文件属性的API,这些API具备平台无关性,不与特性的文件系统相耦合,另外它还提供了标准文件系统的SPI,供各个服务商扩展实现;
  • 提供AIO功能,支持基于文件的异步I/O操做和针对网络套接字的异步操做;
  • 完成JSR定义的通道功能,包括对配置和多播数据报的支持等。

参考文献:

《Netty权威指南》

《UNIX网络编程》

https://my.oschina.net/u/3729778/blog/1798339

相关文章
相关标签/搜索