编者注:Java nio 空轮询bug也就是Java nio在Linux系统下的epoll空轮询问题。java
epoll机制是Linux下一种高效的IO复用方式,相较于select和poll机制来讲。其高效的缘由是将基于事件的fd放到内核中来完成,在内核中基于红黑树+链表数据结构来实现,链表存放有事件发生的fd集合,而后在调用epoll_wait时返回给应用程序,由应用程序来处理这些fd事件。linux
使用IO复用,Linux下通常默认就是epoll,Java NIO在Linux下默认也是epoll机制,可是JDK中epoll的实现倒是有漏洞的,其中最有名的java nio epoll bug就是即便是关注的select轮询事件返回数量为0,NIO照样不断的从select本应该阻塞的Selector.select()/Selector.select(timeout)
中wake up出来,致使CPU 100%问题。以下图所示:
程序员
那么产生这个问题的缘由是什么的?其实在 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6670302 上已经说明的很清楚了,好比下面是bug复现的一个场景:面试
A DESCRIPTION OF THE PROBLEM : The NIO selector wakes up infinitely in this situation.. 0. server waits for connection 1. client connects and write message 2. server accepts and register OP_READ 3. server reads message and remove OP_READ from interest op set 4. client close the connection 5. server write message (without any reading.. surely OP_READ is not set) 6. server's select wakes up infinitely with return value 0
上面的场景描述的问题就是链接出现了RST,由于poll和epoll对于忽然中断的链接socket会对返回的eventSet事件集合置为POLLHUP或者POLLERR,eventSet事件集合发生了变化,这就致使Selector会被唤醒,进而致使CPU 100%问题。根本缘由就是JDK没有处理好这种状况,好比SelectionKey中就没定义有异常事件的类型。数据结构
class SelectionKey { public static final int OP_READ = 1 << 0; public static final int OP_WRITE = 1 << 2; public static final int OP_CONNECT = 1 << 3; public static final int OP_ACCEPT = 1 << 4; }
既然nio epoll bug存在,那么能不能规避呢?答案是有的,好比netty就很巧妙的规避了这个问题,它的处理机制就是若是发生了这种状况,而且发生次数超过了SELECTOR_AUTO_REBUILD_THRESHOLD(默认512),则调用rebuildSelector()进行Selecttor重建,这样就不用管以前发生了异常状况的那个链接了。由于重建也是根据SelectionKey事件对应的链接来从新注册的。框架
该问题最先在 Java 6 发现,随后不少版本声称解决了该问题,但实际上只是下降了该 bug 的出现频率,目前从网上搜索到的资料显示,Java 8 仍是存在该问题(当 Thrift 遇到 JDK Epoll Bug)。socket
最后一块儿来分析下,nio epoll bug不是linux epoll的问题,而是JDK本身实现epoll时没有考虑这种状况,或者说由于其余系统不存在这个问题,Java为了封装(好比SelectionKey 中的4个事件类型)的统一而没去处理?ui
这里思考下,若是想要从java nio层面上来解决这个问题,该如何作呢?this
一种是nio事件类型SelectionKey新加一种"错误"类型,好比针对linux epoll中的epollhup和epollerr,若是出现这种事件,建议程序直接close socket,但这种方式相对来讲对于目前的nio SelectionKey改动有点大,由于SelectionKey的定义目前是针对全部jdk平台的;还有一种是针对jdk nio 对epoll的封装中,对于epoll的epollhup和epollerr事件,epoll封装内部直接处理,好比close socket,可是这种方案也有一点尴尬的是,可能上层应用代码还保留有出现问题的socket引用,这时最好是应用程序可以感知这种状况来处理比较好。3d
Java nio空转问题由来已久,通常程序中是经过新建Selector的方式来屏蔽掉了JDK5/6的这个问题,所以,对于开发者来说,仍是尽可能将JDK的版本更新到最新,或者使用NIO框架如Netty,Grizzly等进行研发,以避免出更多的问题。
推荐阅读
欢迎小伙伴关注【TopCoder】阅读更多精彩好文。