BIO、NIO、AIO

I/O是机器获取和交换信息的主要渠道,而流是完成I/O操作的主要方式。

流分为输入流(InputStream)和输出流(OutputStream)。

在Java的I/O操作类的包下,InputStream,OutputStream以及Reader和Writer类是I/O包中的4个基本类,分别用来处理字节流和字符流。

字节是文件的最小存储单元,为什么还会出现字符流呢?因为我们在字节到字符的时候必须经过转码,这是一个耗时的过程,所以Java直接提供了一个字符流的操作接口。

BIO

传统的IO模型是同步阻塞IO,也称为BIO,所谓同步阻塞,就是指在程序在执行IO过程中,需要等待IO的操作完成,这期间线程处于阻塞状态,不能去处理别的事情。

在传统 I/O 中,InputStream 的 read() 是一个 while 循环操作,它会一直等待数据读取,直到数据就绪才会返回。这就意味着如果没有数据就绪,这个读取操作将会一直被挂起,用户线程将会处于阻塞状态。在少量连接请求的情况下,使用这种方式没有问题,响应速度也很高。但在发生大量连接请求时,就需要创建大量监听线程,这时如果线程没有数据就绪就会被挂起,然后进入阻塞状态。一旦发生线程阻塞,这些线程将会不断地抢夺 CPU 资源,从而导致大量的 CPU 上下文切换,增加系统的性能开销。

具体IO的操作流程如下:

 

  • JVM 会发出 read() 系统调用,并通过 read 系统调用向内核发起读请求;
  • 内核向硬件发送读指令,并等待读就绪;
  • 内核把将要读取的数据复制到指向的内核缓存中;
  • 操作系统内核将数据复制到用户空间缓冲区,然后 read 系统调用返回。

在这个过程中,数据先从外部设备复制到内核空间,再从内核空间复制到用户空间,这就发生了两次内存复制操作。这种操作会导致不必要的数据拷贝和上下文切换,从而降低 I/O 的性能。

 多线程下的BIO模型如上所示,需要通过serverSocket监听所有Socket接口,以便获取通知。

NIO

面对BIO的性能问题,操作系统以及编程语言都进行了优化,提出了非阻塞式IO模型-NIO。

NIO是基于channel(管道)和Buffer(缓冲区)来进行操作的,程序对文件的IO操作必须通过Channel完成,而对Channel的读写必须通过Buffer。

Selector(多路复用器)是NIO中实现非阻塞模式的关键,我们可以用它对各个通道注册比较感兴趣的事件,并调用其select()方法检查和获取各个通道中产生的响应事件的集合,必须新建连接、通道可读、通道可写。这一个机制使我们通过一个线程来一次处理这些事件,而不需要每个通道都启动一个线程。

多路复用器的工作模式:

Buffer(缓冲区)是IO读写都会用到缓冲区,Java提供了一个从堆外分配空间的方法 ByteBuffer.allocateDirect(1024) 以及堆内分配空间的方法 ByteBuffer.allocate(1024);两者都可以实现回收,区别是堆内存访问要比直接内存慢一点,但是便于回收;直接内存分配内存慢一点,但是分配的直接内存会在Buffer对象回收的时候回收,那意味着,如果不处罚Full GC,是不会进行垃圾回收的。

直接内存具体操作流程如下:

 如果单纯使用 Java 堆内存进行数据拷贝,当拷贝的数据量比较大的情况下,Java 堆的 GC 压力会比较大,而使用非堆内存可以减低 GC 的压力。DirectBuffer 则是直接将步骤简化为数据直接保存到非堆内存,从而减少了一次数据拷贝。

AIO

java1.7之后,NIO基础上进行了改建,NIO2.0。其中最重要的就是新增了异步非阻塞IO, 即JAVA AIO。

AIO是由操作系统在IO操作完成后主动通知调用者。在性能方面,AIO的IO过程需要充分调动操作系统参与。因此不同的操作系统存在差异,Linux通过epoll机制来模拟实现AIO,而windows操作系统则通过IOCP机制来实现AIO。

原文地址:https://www.cnblogs.com/wangb0402/p/12704417.html