1 - 传统IO的性能问题

多次内存复制

不管是磁盘IO还是网络IO

  1. 数据总是先从硬件设备复制到内核空间
  2. 再从内核空间复制到用户空间

这就发生了两次数据拷贝和上下文切换(内核态和用户态的一次切换就是一次上下文切换),这就降低了性能。

阻塞

传统IO在没有数据可读时,读操作就会被挂起,用户线程会一直阻塞。当有大量这样的线程阻塞时,这些线程会不断的抢夺CPU资源,导致大量的CPU上下文切换,增加了系统的开销。

2 - 如何解决性能问题

jdk1.4发布的NIO,优化了内存复制和阻塞导致的性能问题,JDK1.7 又发布了 NIO2,提出了从操作系统层面实现的异步 I/O。

具体优化手段如下:

2.1 - 使用Buffer优化读写操作

NIO提出了Buffer和Channel的概念。
Buffer:一块连续的内存空间,是NIO读写数据的中转地。
Channel: 表示数据的源头或者目的地。用于从Buffer中读取和写入数据

传统IO和NIO最大的区别之一就是,传统IO面向流,NIO面向Buffer。使用Buffer,Channel可以将数据一次性写入或者读取。

2.2 - 减少数据复制

减少数据的复制有两个方面:

  1. 减少用户空间内的内存复制。使用DirectBuffer
  2. 减少用户空间和内核空间之间的内存复制。使用MappedByteBuffer

NIO提供了DirectBuffer,用于直接访问物理内存。和普通Buffer的区别:

  1. 普通Buffer分配的事JVM堆内存
  2. DirectBuffer分配的是物理内存(也称为堆外内存和非堆内存)

前面我们提到过,数据要写入到外部设备,需要先从用户空间复制到内核空间,再由内核空间复制到硬件设备。但是在用户空间中,还存在另一个数据复制。就是从堆内存复制到非堆内存。这个过程是通过DirectBuffer来实现的。

image.png
Java使用直接内存进行数据拷贝的原因:减少堆的GC压力。如果直接使用堆内存进行数据拷贝,在数据量比较大时,堆的GC压力也是比较大的。

2.3 - 避免阻塞,优化IO操作

IO模型:使用IO多路复用

线程模型:主从Reactor线程模型