Java IO/NIO

Java IO/NIO详解

  • 简介
  • IO类型详述
    • BIO详解
    • NIO
    • 异步AIO详解
  • IO相关新硬件
  • 磁盘IO
  • 网络IO
  • 常用通信框架

简介

IO是Java中重要特性,按照IO设备类型,分为磁盘IO和网络IO;按照IO实现方式,分为同步/异步IO、阻塞/非阻塞IO。

Java中有BIO、NIO(None-Block IO)、AIO三种IO类型分装,分别对应同步阻塞IO(BIO)、同步非阻塞IO(NIO)、异步非阻塞IO(AIO)。

IO类型详述

BIO详解

BIO全称为Blocking I/O, 是同步阻塞IO,最传统的IO模型,包括文件操作和网络通信,均是基于字节流的处理;这种IO方式效率最低,一个问题是这种IO方式属于阻塞IO,执行时候,线程不能干其它事情,必须执行完IO操作(包括这其中的等待);另一个问题是传统IO方式,需要经过用户态内存、内核态buffer和IO设备之间,这中间要经过两次copy和几次中断,在通信密集和数据密集型系统中效率地下。

NIO

Java中的实现为NIO,全称为Non-Blocking I/O, 严格说是将异步和阻塞结合起来,是对通知事件进行阻塞,而不是对 I/O 调用进行阻塞,基于select产生阻塞,IO操作为异步。Java对于该IO模型进行了很好的封装。对于网络IO,基于epoll进行封装;对于网络IO,有些成熟的框架可以使用,如Netty、firenio等,结合合理的报文设计和IO预取策略,可以达到很好的性能;对于磁盘IO,Java封装的FileChannel、DirectByteBuffer、MappedByteBuffer(mmap的封装,但是清理操作需要一些特殊的技术手段)、zero-copy(基于transferTo, 减少buffercopy和中断,如果有专门硬件设备,可以取得更好的性能)。

异步AIO详解

Java对于AIO的支持不是特别好,AIO在C/C++这种语言实现的底层系统中能够得到很好的应用,异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明 read 请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。

Java在实现异步读写操作时候,通过JVM自己的线程类实现操作.

AIO还会有一次用户态和内核态的拷贝动作,使用异步 I/O 可以帮助我们构建 I/O 速度更快、效率更高的应用程序。如果我们的应用程序可以对处理和 I/O 操作重叠进行,那么 AIO 就可以帮助我们构建可以更高效地使用可用 CPU 资源的应用程序。尽管这种 I/O 模型与在大部分 Linux 应用程序中使用的传统阻塞模式都不同,但是异步通知模型在概念上来说却非常简单,可以简化我们的设计。

AIO可以更好的提高线程复用度,且能进行并行IO操作,如果对数据进行合理切分,那么只要磁盘IO带宽允许,并行AIO操作可以显著加快IO速度。如果操作系统层能够支持sendfile/zero-copy/mmap等方式的AIO,那就更完美了。JDK在这个基础上再进行封装,世界就会如此美好。

IO相关新硬件

硬件技术在持续进化,现在可以使用与传统 DRAM 具有相同接口和类似性能特点的非易失性 RAM。Java 10 中将使得 JVM 能够使用适用于不同类型的存储机制的堆,在可选内存设备上进行堆内存分配。

一些操作系统中已经通过文件系统提供了使用非 DRAM 内存的方法。例如:NTFS DAX 模式和 ext4 DAX。这些文件系统中的内存映射文件可绕过页面缓存并提供虚拟内存与设备物理内存的相互映射。与 DRAM 相比,NV-DIMM 可能具有更高的访问延迟,低优先级进程可以为堆使用 NV-DIMM 内存,允许高优先级进程使用更多 DRAM。

要在这样的备用设备上进行堆分配,可以使用堆分配参数 -XX:AllocateHeapAt = ,这个参数将指向文件系统的文件并使用内存映射来达到在备用存储设备上进行堆分配的预期结果。

磁盘IO

磁盘IO,典型的交互包括用户态内存、内核态buffer、磁盘控制器。每次普通IO操作均经过这三个进行操作,其中内核态buffer作为中转桥梁,进行数据对齐操作;因为,磁盘操作数据是以块为单位的,往往对于实际请求。

操作系统提供了mmap机制,再用户态和内核态共享buffer取,能够减少一次内存copy,同时借助虚拟文件系统的缺页机制,不断load数据到mmap的buffer中。

同时,对于顺序读IO操作,操作系统的pagecache能够提供很好的性能;但是对于随机读,pagecache反而成为一个累赘,这种情况下,应该采用DIO,绕过pagecache,设计自己的LRU Cache,进行数据访问。

IO测试方法: fio命令测试磁盘读写IO能力、iostat -m 1命令可以1秒钟以M为单位输出一次IO吞吐量

基于mmap进行mergeio,对数据聚合

网络IO

RDMA、其它硬件技术、网络通信模型(如长短连接)

经典的系统数据流转中,系统收到用户请求->从文件读取数据->发送数据给用户,这中间如果文件数据格式设计合适,可以通过sendfile方式,直接将数据发送给用户,实现zero-copy。

如果有RDMA设备,还可以绕过系统主内存,直接将数据传输至对方CPU的内存中,但是这种使用方式涉及底层,必须硬件支持,同时使用C/C++语言比较合适,或者针对Java进行封装。

在网络IO中,频繁的创建和销毁连接,是个比较耗时的操作,在高并发系统,建议使用长连接。

常用通信框架

常用网络通信框架:netty、firenio、MINA、RDMA、LRU Cache(可以结合AIO搞)

磁盘IO操作: 随机读使用O_DIRECT方式的直接IO(绕过pagecache)、LRU Cache(可以结合AIO搞)

如果需要结合磁盘IO和网络IO进行处理,需要自己实现。

需要注意的是,Java中ByteBuffer的read/write实现中,jdk会自己创建一个临时DirectByteBuffer(DirectByteBuffer还是用户态内存)的堆外空间作为缓冲,这里使用不合理,容易造成堆外内存溢出。

对于数据密集型系统,除了计算时间复杂度意外,同时还要计算空间复杂度。精确计算空间使用情况,数据对象尽量复用,减少频繁创建和销毁,并合理利用堆外空间;对系统的GC优化有好的效果,需要注意调整堆外内存大小,年轻代比例,gc算法。

综合考虑系统业务特点,设计合适的IO策略和GC策略,对系统吞吐量能带来很好的提高。

链接:
数据库随机IO优化总结
走进科学之揭开神秘的"零拷贝"!
深入浅出MappedByteBuffer
MappedByteBuffer的一点优化
构建通讯项目的异步 io 框架 FireNio
JAVA IO 以及 NIO 理解
java nio及操作系统底层原理
使用异步 I/O 大大提高应用程序的性能
linux AIO (异步IO) 那点事儿
在 Java 7 中体会 NIO.2 异步执行的快乐

你可能感兴趣的:(Java)