大家好,我是小水珠。
提到JavaI/O,相信你一定不陌生。你可能使用I/O操作读写文件,也可能使用它实现Socket的信息传播...这些都是我们在系统中最常遇到的和I/O有关的操作。 今天,我们就来深入了解下JavaI/O在高并发,大数据业务场景下暴露出的性能问题,从源头入手,学习优化方法。
一 什么是I/O
I/O是机器获取和交换信息的主要通道,而流是完成I/O操作的主要方式。
Java的I/O操作类在包java.io下,其中InputStream,OutputStream以及Reader,Writer类是I/O包中的4个基本类,他们分别处理字节流和字符流。如下图所示:
1.字节流
2.字符流
二 传统I/O的性能问题
1.多次内存复制
2.阻塞
三 如何优化I/O操作
1.使用缓冲区优化读写流操作
2.使用DirectBuffer减少内存复制
你肯定会想,为什么Java需要通过一个临时的非堆内存来复制数据呢?如果单纯使用Java堆内存进行数据拷贝,当拷贝的数据量比较大的情况下,Java堆的GC压力会比较大,而使用非堆内存可以降低GC压力。
DirectBuffer则是直接将步骤简化为数据直接保存到非堆内存,从而减少了一次数据拷贝。以下是JDK调用IOUtil.java类中的write方法:
3.避免阻塞。优化I/O操作
NIO很多人也称之为非阻塞IO。因为这样叫,更能体现它的特点,为什么这么说呢?
通道(Channel)
Channel有自己的处理器,可以完成内核空间和磁盘之间的I/O操作。在NIO中,我们读取和写入数据都需要通过Channel,由于Channel是双向的,所以读,写可以同时进行。
多路复用器(Selector)
Selector是Java NIO编程的基础。用于检查一个或多个NIO Channel的状态是否处于可读,可写。
目前操作系统的I/O多路复用器机制都使用了epoll,相比传统的select机制,epoll没有最大连接句柄1024的限制。所以Selector在理论上可以轮训成千上万的客户端。
四 总结
Java的传统I/O最开始是基于InputStream和OutputStream这两个操作实现的,这种流操作是以字节为单位,如果在高并发,大数据场景中,很容易导致阻塞,因此这种操作的性能是非常差的。还有,输出数据从用户空间复制到内存空间,在复制到输出设备,这样的操作会增加系统的性能开销。
传统I/O后来使用了Buffer优化“阻塞”这个性能问题,以缓冲块作为最小单位,但相比整体性能来说依然不尽人意。
于是NIO发布,他是基于缓冲块为单位的流操作,在Buffer的基础上,新增了两个组件“管道和多路复用器”,实现了非阻塞I/O,实现了非阻塞I/O,NIO适用于发生大量I/O连接请求的场景,这三个组件共同提升I/O的整体性能。