NIO
正反两面都能考虑到,说出道理来,才真正了解这件事情
什么是NIO?
Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O,传统IO是基于字节流和字符流进行操作(基于流),而NIO基于Channel和Buffer(缓冲区)进行操作
四种主要的IO模型?
- 同步阻塞IO(Blocking IO)
- 同步非阻塞IO(Non-blocking IO)
- IO多路复用(IO Multiplexing)
- 异步IO(Asynchronous IO)
IO如何操作?
I/O操作可以分为3类:同步阻塞(即早期的BIO操作)、同步非阻塞(NIO)、异步非阻塞(AIO)。
1、NIO采用的是一种多路复用的机制,利用单线程轮询事件,高效定位就绪的Channel来决定做什么,只是Select阶段是阻塞式的,能有效避免大量连接数时,频繁线程的切换带来的性能或各种问题。
2、BIO在此种方式下,用户进程在发起一个I/O操作后,必须等待I/O操作的完成,只有当真正完成了I/O操作以后,用户进程才能运行。JAVA传统的I/O模式属于此种方式。
3、AIO此种方式下是指用户进程发起一个I/O操作以后,不等待内核I/O操作的完成,内核完成I/O操作以后会通知用户进程,JDK1.7中,这部分内容被称作NIO.2Linux上AIO不够成熟
java IO读写的底层流程?
用户程序进行IO的读写,基本上会用到系统调用read&write,read把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区,它们不等价于数据在内核缓冲区和磁盘之间的交换。
堵塞IO有什么弊端?
现阻塞I/O存在一些缺点。根据阻塞I/O通信模型,我总结了它的两点缺点:
1、当客户端多时,会创建大量的处理线程。且每个线程都要占用栈空间和一些CPU时间
2、阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。
在这种情况下非阻塞式I/O就有了它的应用前景。
NIO中三个重要角色?
NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统IO是基于字节流和字符流进行操作(基于流),而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
Buffer
Buffer(缓冲区)是一个用于存储特定基本类型数据的容器。除了boolean外,其余每种基本类型都有一个对应的buffer类。Buffer类的子类有ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer 。
Channel
Channel(通道)表示到实体,如硬件设备、文件、网络套接字或可以执行一个或多个不同 I/O 操作(如读取或写入)的程序组件的开放的连接。Channel接口的常用实现类有FileChannel(对应文件IO)、DatagramChannel(对应UDP)、SocketChannel和ServerSocketChannel(对应TCP的客户端和服务器端)。Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream.而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。
Selector
Selector(选择器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。即用选择器,借助单一线程,就可对数量庞大的活动I/O通道实施监控和维护。
如图所示!
多路复用IO工作大致工作原理?
1、 由一个专门的线程来处理所有的 IO 事件,并负责分发。
2、 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
3、 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。
NIO服务器端如何实现非阻塞?
服务器上所有Channel需要向Selector注册,而Selector则负责监视这些Socket的IO状态(观察者),当其中任意一个或者多个Channel具有可用的IO操作时,该Selector的select()方法将会返回大于0的整数,该整数值就表示该Selector上有多少个Channel具有可用的IO操作,并提供了selectedKeys()方法来返回这些Channel对应的SelectionKey集合(一个SelectionKey对应一个就绪的通道)。正是通过Selector,使得服务器端只需要不断地调用Selector实例的select()方法即可知道当前所有Channel是否有需要处理的IO操作。注:java NIO就是多路复用IO,jdk7之后底层是epoll模型。
目前的常用的IO复用模型有三种:select,poll,epoll。
(1)select==>时间复杂度O(n)
它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
(2)poll==>时间复杂度O(n)
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
(3)epoll==>时间复杂度O(1)
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))
select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现
select的优点是?
select有最大并发数限制,epoll比select优点很多,不过select的优点是
在并发数低 socket都较活跃时 可能出现select优于epoll的情况,良好的兼容性,epoll 只能用在linux下
select随意
用netty时发现是linux就可以很开心的用EpollGroup了,
netty会通过不同的操作系统选择nio的模型
NioEventLoop底层会根据系统选择select或者epoll。如果是windows系统,则底层使用WindowsSelectorProvider(select)实现多路复用;如果是linux,则使用epoll
流行基于Java NIO通信框架
Mina、Netty、Grizzly等
它们的出身
Mina 开发方Apache;
Netty 开发方Jboss;
Grizzly 开发方Sun
Netty应用比较广泛,原因Netty提供了相对十分简单易用的API,非常适合网络编程。Netty市场占有率比较高,成熟、稳定、综合性能高、社区活跃。
Mina将内核和一些特性的联系过于紧密,使得用户在不需要这些特性的时候无法脱离,相比下性能会有所下降,Netty解决了这个设计问题,Mina对UDP进行了高级层次的抽象,而要Netty做到这一点比较困难
Netty比Mina使用起来更简单,它们的架构差别不大
Mina和Netty这两个NIO框架的创作者是同一个人Trustin Lee(韩国人) 。Netty从某种程度上讲是Mina的延伸和扩展,解决了一些Mina上的设计缺陷,也优化了一下Mina上面的设计理念。
Netty的epoll 空轮询问题?
selector在没有结果的情况下,依然被唤醒,
导致一直空轮询,cpu100%
netty 解决:
先定义当前时间currentTimeNanos。
接着计算出一个执行最少需要的时间timeoutMillis。
每次对selectCnt做++操作。
进行判断,如果到达执行到最少时间,则seletCnt重置为1。
一旦到达SELECTOR_AUTO_REBUILD_THRESHOLD这个阀值,就需要重建selector来解决这个问题。
这个阀值默认是512。
猜猜Netty 为什么没用AIO?
官方原文????
Not faster than NIO (epoll) on unix systems (which is true)
There is no daragram suppport
Unnecessary threading model (too much abstraction without usage)
1、Netty不看重Windows上的使用,在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易深度优化
2、Netty整体架构是reactor模型, 而AIO是proactor模型, 混合在一起会非常混乱,把AIO也改造成reactor模型看起来是把epoll绕个弯又绕回来
3、AIO还有个缺点是接收数据需要预先分配缓存, 而不是NIO那种需要接收时才需要分配缓存, 所以对连接数量非常大但流量小的情况, 内存浪费很多
4、Linux上AIO不够成熟,处理回调结果速度跟不到处理需求,比如外卖员太少,顾客太多,供不应求,造成处理速度有瓶颈(待验证)
5、netty官方说liunx上NIO比AIO快
NIO和AIO
NIO:会等数据准备好后,再交由应用进行处理,数据的读取/写入过程依然在应用线程中完成,只是将等待的时间剥离到单独的线程中去,节省了数据准备时间,因为多路复用机制,Selector会得到复用,对于那些读写过程时间长的,NIO就不太适合。
AIO:读完(内核内存拷贝到用户内存)了系统再通知应用,使用回调函数,进行业务处理,AIO能够胜任那些重量级,读写过程长的任务。
也就是说:AIO是发出IO请求后,由操作系统自己去获取IO权限并进行IO操作;NIO则是发出IO请求后,由线程不断尝试获取IO权限,获取到后通知应用程序自己进行IO操作。NIO是同步非堵塞,AIO是异步非堵塞
释放
jvm还有一个堆外内存,也就是直接内存,这个是专门为了io和nio设计的,Java底层使用c语言的API调用操作系统与io进行交互,这个堆外内存不受GC控制,所以每用一次都要释放,举个例子,如Java内存中有一个字节数组,我们调用流将它写入磁盘文件,那jvm需要先将其copy到堆外内存,然后调用c语言的API将其写入磁盘。