文章目录
- 零、为什么需要`Java NIO`
- 原因
- 一、`Java NIO` 与 `IO` 区别
- (1) 面向流 与 面向缓冲
- (2) 阻塞 与 非阻塞IO
- 1. 阻塞
- 2. 非阻塞
- (3) 选择器(Selector)
- 二、I/O 概念
- (1) 缓冲区
- 1. I/O操作
- 2. 用户空间与内核空间
- (2) 虚拟内存
- (3) 文件 I/O
- 1. 内存映射文件
- 三、参考资料
零、为什么需要Java NIO
早期关注点在与
JVM
, 而现在 JVM
运行字节码的速率已经接近本地编译代码, 借助动态运行时优化, 其表现甚至还有所超越.
意味着: 多数 Java 应用程序已不再受 CPU 的束缚(把大量时间用在执行代码上), 而更改多时候是受 I/O 的束缚 (等待数据传输)
原因
操作系统 与 Java 基于流的I/O模型有些不匹配
操作系统要移动的是大块数据 (缓冲区), 这往往是在硬件直接存储器存取 (DMA) 的协助下完成的
JVM
的I/O
类喜欢操作小块数据 — 单个字节、几行文本
结果是: 操作系统送来整个缓冲区的数据, Java IO
的流数据类再花大量时间把它们拆成小块, 往往拷贝一小块就要往返于几层对象
即: 操作系统喜欢整卡车运来数据,
Java IO
类 则喜欢一铲子一铲子地加工数据
SO: 有了 NIO
, 就可以轻松地把一卡车数据备份到能直接使用的地方 (ByteBuffer)
Tips: 这里的备份, 需要注意下, 之后会有优化, 如 零拷贝(zero copy)
一、Java NIO
与 IO
区别
Java NIO
: 面向缓冲、非阻塞IO、选择器
IO
: 面向流、 阻塞IO、 无
(1) 面向流 与 面向缓冲
面向流: 每次从流中读一个或多个字节, 直至读取所有字节, 它们没有被缓存在任何地方, 不能前后移动流中的数据
面向缓冲: 数据读取到一个它稍后处理的缓冲区, 需要时可在缓冲区中前后移动, 增加了处理过程中的灵活性
(2) 阻塞 与 非阻塞IO
1. 阻塞
Java IO
的各种流是阻塞的
当一个线程调用read()
或 write()
时, 该线程被阻塞, 直到有一些数据被读取, 或数据完全写入, 期间该线程不能再干任何事情了
2. 非阻塞
一个线程从某个通道发送请求读取数据, 若有数据则读取, 若无则继续做其他的事情.
线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作, 所有一个单独的线程可以管理多个输入和输出通道 (Channel)
(3) 选择器(Selector)
Java NIO
的 选择器允许一个单独的线程来监视多个输入通道
二、I/O 概念
(1) 缓冲区
1. I/O操作
- 进程执行 I/O 操作, 向操作系统发送请求, 如
read()
系统调用, 要求其将缓冲区填满 - 内核随即向磁盘控制硬件发出命令, 要求从磁盘读取数据
- 磁盘控制器把数据直接写入内核内存缓冲区, 这一步通过
DMA
完成, 无虚 CPU - 一旦磁盘控制器把缓冲区装满, 内核随即把数据从内核空间的临时缓冲区拷贝到进程执行
read()
调用时指定的缓冲区
I/O缓冲区操作简图:
2. 用户空间与内核空间
- 用户空间是常规进程所在区域
JVM
就是常规进程, 驻守于用户空间
用户空间是非特权区域, 比如: 在该区域执行的代码就不能直接访问硬件设备
- 内核空间就是操作系统所在区域
内核有特别的权利: 它能与设备控制器通讯, 控制着用户区域进程的运行状态.
- 为什要把数据从内核空间拷贝到用户空间?
- 硬件通常不能直接访问用户空间
- 磁盘这种基于块存储的硬件设备操作的是固定大小的数据块, 而用户进程请求的可能是任意大小的或非对齐的数据块
- 在数据往来于用户空间与存储设备的过程中, 内核负责数据的分解、再组合工作, 因此充当着中间人的角色
- 发散 / 汇聚
为了高效操作, 进程只需一个系统调用, 就能把一连串缓冲区地址传递给操作系统
如图:
(2) 虚拟内存
虚拟内存: 使用虚拟地址取代物理(硬件 RAM)内存地址
好处如下:
- 一个以上的虚拟地址可指向同一个物理内存地址
- 虚拟内存空间可大于实际可用的硬件内存
因为设备控制器不能通过 DMA 直接存储到用户空间
若把内核空间地址与用户空间的虚拟地址映射到同一个物理地址, 这样, DMA 硬件(只能访问物理内存地址) 就可以填充对内核与用户空间进程同时可见的缓冲区
这样做, 可以省去内核与用户空间的往来拷贝
前提:
内核与用户缓冲区必须使用相同的页对齐, 缓冲区的大小还必须是磁盘控制器大小 (通常为 512 字节的磁盘扇区) 的倍数
内存空间多重映射图:
(3) 文件 I/O
1. 内存映射文件
大多数操作系统都支持的特殊类型的 I/O 操作, 允许用户进程最大限度地利用面向页的系统 I/O 特性, 并完全摒弃缓冲区拷贝
用户内存到文件系统页的映射图:
好处:
- 用户进程把文件数据当作内存,所以无需发布 read( )或 write( )系统调用。
- 当用户进程碰触到映射内存空间,页错误会自动产生,从而将文件数据从磁盘读进内存。如果用户修改了映射内存空间,相关页会自动标记为脏,随后刷新到磁盘,文件得到更新。
- 操作系统的虚拟内存子系统会对页进行智能高速缓存,自动根据系统负载进行内存管理
- 数据总是按页对齐的,无需执行缓冲区拷贝。
- 大型文件使用映射,无需耗费大量内存,即可进行数据拷贝。
三、参考资料
- http://ifeve.com/java-nio-vs-io/
- << Java NIO >>