Java NIO分析(12): NIO总结
迟来的总结,NIO系列写了11篇了,本篇做个总结吧
写这个系列的起因是各个框架比如netty
, tomcat
, jetty
这些高性能框架的
基石就是NIO
, 一直想讲讲它们高性能的原因。
专注于人工智能(AI)、JVM(openjdk)、云计算、效率工具、软件架构
迟来的总结,NIO系列写了11篇了,本篇做个总结吧
写这个系列的起因是各个框架比如netty
, tomcat
, jetty
这些高性能框架的
基石就是NIO
, 一直想讲讲它们高性能的原因。
前面我们详细讲了Java-NIO分析-8-Selector详解和Java-NIO分析-9-从BSD-socket到SocketChannel, 分别是NIO的事件分发器和非阻塞处理器.
为了支持Channel
的双向读写和Scatter/Gather
操作,我们还需要Buffer
,将I/O数据存储备用。普通的Buffer都是JVM堆内的Buffer, 比较好理解.
接下来我们聊聊JVM使用堆外内存的沧桑历史以及为什么要设计出DirectBuffer
。
前面我们讲了高并发核心Selector
的源码分析,看到其对操作系统I/O多路复用的简单封装。
有了I/O多路复用之后,我们还需要非阻塞的socket读写操作.
因为内核告诉你A连接有数据可读,你想要读1K, 事实上只读到了0.5K, 如果使用传统的 socket API, 那么进程或者线程会在这里阻塞,浪费了CPU的时钟周期和珍贵的线程资源。 使用非阻塞就能在没有读满之前立刻返回,数据先放内存里,然后继续读下一个B连接的数据。
SocketChannel
就是NIO对于非阻塞socket操作的支持的组件,其在socket上封装了一层, 所以我们先从Socket API
说起。
上节Java-NIO分析-7-NIO核心分析之Channel-Buffer和Selector介绍了Channel
,Buffer
和Selector
的基本用法
有了感性认识之后,来看看Selector的底层是如何实现的。
笔者下载得是openjdk8的源码, 画出类图
比较清晰得看到,openjdk中Selector的实现是SelectorImpl
,
然后SelectorImpl又将职责委托给了具体的平台,比如图中框出的linux2.6以后才有的EpollSelectorImpl
, Windows平台则是WindowsSelectorImpl
, MacOSX
平台是KQueueSelectorImpl
.
从名字也可以猜到,openjdk肯定在底层还是用epoll
,kqueue
,iocp
这些技术来实现的I/O多路复用。前面 Java-NIO分析-3-I-O多路复用之select系统调用 ,Java-NIO分析-4-I-O多路复用之poll系统调用 , Java-NIO分析-5-I-O多路复用之epoll系统调用写了3篇来说明其用法,感兴趣的读者可以回头看看。
上次Java-NIO分析-6-Java-NIO中的概念讲到了NIO的设计思想,
即Doug Lea
大佬受AWT
启发得到的事件驱动机制, 关键点在于
在NIO的API中,Channel
就是实现非阻塞的组件,而事件分发(Dispatcher)使用的是Selector
组件,
在传统的I/O流(Stream
)是有方向的,而NIO支持双向读写,这样就需要将流中的数据读取到某个缓冲组件里,
即Buffer
组件.
Buffer
组件还有个特殊的实现DirectByteBuffer
, 可以申请堆外内存,关于为什么要申请堆外内存后续会谈。
前面介绍了Unix的I/O模型以及多路复用的c实现,为什么要介绍这些呢? 因为JVM是用c++写的,JDK的native方法也都是用c写的,最后它们调用 的还是操作系统底层的api,所以了解一些关键的底层原理还是有必要的。
……poll
系统调用相比于select
主要解决了文件描述符的数量限制,但是在高并发场景下没有解决根本问题:
这俩性能问题在Banga在1999年写了篇论文A Scalable and Explicit Event
Delivery Mechanism for UNIX,提出select
和poll
都是无状态的,需要用户空间的进程自行遍历查找事件, 一种改进方案是内核内部自己维护事件集合.通过一个类似declare_interest
的系统调用,内核能够增量得更新进程感兴趣的事件集合列表, 应用进程通过使用get_next_event
调用能派发新事件给内核。
根据论文的研究成果,LINUX
和FreeBSD
各自给出的解决方案:epoll
和kqueue
.我们主要讨论epoll, 毕竟日常服务端环境都是LINUX.
在LINUX内核2.6以上,epoll
才受到支持。
poll
系统调用主要解决了select
系统调用的2个问题:
值-结果参数
的api设计不是很好, select系统调用的时候要分别传读set,写set,更多事件不好细分poll
系统调用使用了pollfd数据结构来表示事件数组,没有了fd_setsize
的限制,同时支持更多的事件类型
前面讲了一些Java-NIO分析-2-I-O多路复用历史杂谈, 谈到了多路复用的发展历史
以及为什么需要它。今天讲广受各大内核支持的select
系统调用,select
允许进程
指定内核等待1个或者多个事件的任何一个发生, 并且只在有它们发生之后或者等待一段时间后才唤醒进程。
前面Java-NIO分析-1-Unix网络模型讲过5种经典I/O模型, 现代企业的场景一般是高并发高流量,长连接, 假设硬件资源充足,如何提高应用单机能接受链接的上限? 先讲段历史
……