編者注: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】閱讀更多精彩好文。