Java NIO

时间:2020-08-18 13:31:22   收藏:0   阅读:54

1.1 基础知识

首先我们知道进程是无法直接操作I/O设备的,其必须通过系统调用请求内核来协助完成I/O动作,而内核会为每个I/O设备维护一个buffer。
技术图片
这里数据的两次拷贝都需要时间,而这两端时间中进程和内核的状态不一样就产生了下面五种i/o模型:

换种方式理解,最初的io模型中,关注的信息是->我要读写了。这种情况下,等待数据准备好和拷贝数据都阻塞,然后进一步的nio模型中,关注的信息是->我可以读写了。这种情况下socket主要的读、写、注册和接收函数在等待的时候都是非阻塞的,只是在最后的拷贝阶段是同步阻塞的。但性能非常高。然后最新的aio中,最后一步操作都是非阻塞的。完成后直接给线程一条数据准备完成的信号就可以了。

1.2零拷贝技术

CPU不执行数据从一个存储区域到另一个存储区域的拷贝任务,这通常用于在网络上传输文件时节省CPU周期和内存带宽。
首先我们需要知道的大多数文件系统的默认 IO 操作都是缓存 IO。也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。也就是说在一次网络传输过程中,数据大概会经历四次copy:
技术图片
其中还发生了多次上下文切换,这样确实加大了cpu的负担,我们可以怎样优化呢?

  1. 首先(通过DMA)将数据从磁盘读取到kernel buffer中;
  2. 然后将kernel buffer拷贝到socket buffer中;
  3. 最后将socket buffer中的数据copy到网卡设备(protocol engine)中发送;
    技术图片
    很显然这样减少了一次拷贝
    当然还可以继续优化。我们还可以直接将数据直接由内存写入socket buffer(或者说将kernel buffer和socket buffer合并)这样就形成了真正的零拷贝。
    具体实现:
  4. 将文件拷贝到kernel buffer中;
  5. 向socket buffer中追加当前要发生的数据在kernel buffer中的位置和偏移量;
  6. 根据socket buffer中的位置和偏移量直接将kernel buffer的数据copy到网卡设备(protocol engine)中;

1.3NIO 直接缓冲区和非直接缓冲区

非直接缓冲区是直接通过拷贝的形式传递的,如从磁盘读取文件到物流空间,然后拷贝到jvm,再读取数据。直接缓冲区是通过物流内存映射文件直接传递的
正常情况下,JVM创建一个缓冲区的时候,实际上做了如下几件事:

  1. JVM确保Heap区域内的空间足够,如果不够则使用触发GC在内的方法获得空间;
  2. 获得空间之后会找一组堆内的连续地址分配数组, 这里需要注意的是,在物理内存上,这些字节是不一定连续的;
  3. 对于不涉及到IO的操作,这样的处理没有任何问题,但是当进行IO操作的时候就会出现一点性能问题.

所有的IO操作都需要操作系统进入内核态才行,而JVM进程属于用户态进程, 当JVM需要把一个缓冲区写到某个Channel或Socket的时候,需要切换到内核态.
而内核态由于并不知道JVM里面这个缓冲区存储在物理内存的什么地址,并且这些物理地址并不一定是连续的(或者说不一定是IO操作需要的块结构),所以在切换之前JVM需要把缓冲区复制到物理内存一块连续的内存上, 然后由内核去读取这块物理内存,整合成连续的、分块的内存.
也就是说如果我们这个时候用的是非直接缓存的话,我们还要进行“复制”这么一个操作,而当我们申请了一个直接缓存的话,因为他本是就是一大块连续地址,我们就可以直接在它上面进行IO操作,省去了“复制”这个步骤
当然缺点也是有的,他的分配和释放都比较昂贵,而且容易发生内存泄露。

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!