NIO的epoll空輪詢bug

JDK NIO的BUG,例如臭名昭著的epoll bug,它會致使Selector空輪詢,最終致使CPU 100%。官方聲稱在JDK1.6版本的update18修復了該問題,可是直到JDK1.7版本該問題仍舊存在,只不過該BUG發生機率下降了一些而已,它並無被根本解決。該BUG以及與該BUG相關的問題單能夠參見如下連接內容。前端

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719java

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933linux

參考:https://github.com/netty/netty/issues/327git

參考:https://www.jianshu.com/p/d0f06b13e2fb程序員

參考:http://blog.jobbole.com/105564/github

參考:http://blog.csdn.net/xyls12345/article/details/26571699設計模式

Selector BUG出現的緣由

若Selector的輪詢結果爲空,也沒有wakeup或新消息處理,則發生空輪詢,CPU使用率100%,前端框架

Netty的解決辦法

  • 對Selector的select操做週期進行統計,每完成一次空的select操做進行一次計數,服務器

  • 若在某個週期內連續發生N次空輪詢,則觸發了epoll死循環bug。網絡

  • 重建Selector,判斷是不是其餘線程發起的重建請求,若不是則將原SocketChannel從舊的Selector上去除註冊,從新註冊到新的Selector上,並將原來的Selector關閉。

 

參考:http://blog.csdn.net/baiye_xing/article/details/73351330

 

前面講到了epoll的一些機制,與select和poll等傳統古老的IO多路複用機制的一些區別,這些區別實質能夠總結爲一句話,

就是epoll將重要的基於事件的fd集合放在了內核中來完成,由於內核是高效的,因此不少關於fd事件監聽集合的操做也是高效的,

不方便的就是,由於在內核中,因此咱們須要經過系統調用來調用關於fd操做集合,而不是直接本身攢一個。

若是在linux中,epoll在JDK6中還須要配置,在後續的版本中爲JDK的NIO提供了默認的實現,可是epoll在JDK中的實現倒是漏洞百出的,

bug很是的多,比較容易復現而且被衆多人詬病的就是epoll輪詢的處理方法。

sun的bug列表爲:

JDK-6670302 (se) NIO selector wakes up with 0 selected keys infinitely [lnx 2.4]

JDK-6670302 : (se) NIO selector wakes up with 0 selected keys infinitely [lnx 2.4]

===》這個bug的描述內容爲,在NIO的selector中,即便是關注的select輪詢事件的key爲0的話,NIO照樣不斷的從select本應該阻塞的

狀況中wake up出來,也就是下圖中的紅色阻塞的部分:



而後,由於selector的select方法,返回numKeys是0,因此下面本應該對key值進行遍歷的事件處理根本執行不了,又回到最上面的while(true)循環,循環往復,不斷的輪詢,直到linux系統出現100%的CPU狀況,其它執行任務幹不了活,

最終致使程序崩潰。

==》從這個bug上來看,這個絕對是JDK中的問題,select方法就應該是阻塞的,沒有key事件過來,那麼就不該該返回,和應用程序的寫法沒有任何的關係,與之相差很少的一個bug給出瞭解決的方案:

JDK-6403933 (se) Selector doesn't block on Selector.select(timeout) (lnx)

JDK-6403933 : (se) Selector doesn't block on Selector.select(timeout) (lnx)

這個bug的意思基本上和前面的JDK-6670302相差不大,也是Selector不阻塞,前一個bug說明的是最終的現象,

這個JDK-6403933的bug說出了實質的緣由:

具體解釋爲,在部分Linux的2.6的kernel中,poll和epoll對於忽然中斷的鏈接socket會對返回的eventSet事件集合置爲POLLHUP,也多是POLLERR,eventSet事件集合發生了變化,這就可能致使Selector會被喚醒。==》這是與操做系統機制有關係的,JDK雖然僅僅

是一個兼容各個操做系統平臺的軟件,但很遺憾在JDK5和JDK6最初的版本中(嚴格意義上來將,JDK部分版本都是),這個問題並無解決,而將這個帽子拋給了操做系統方,這也就是這個bug最終一直到2013年才最終修復的緣由,最終影響力太廣。

修復的方法,在這個bug中已經提到了:

上面是第一個建議,首先將SelectKey去除掉,而後「刷新」一下Selector,刷新的方式也就是調用Selector.selectNow方法,

這個示意的代碼以下:

這段代碼意味着重置,首先將SelectionKey註銷掉,而後從新調用非阻塞的selectNow來讓Selector換取「新生」。

這種修改方式就是grizzly的commiteer們最早進行修改的,而且經過衆多的測試說明這種修改方式大大下降了JDK NIO的問題。

可是,這種修改仍然不是可靠的,一共有兩點:

1.多個線程中的SelectionKey的key的cancel,極可能和下面的Selector.selectNow同時併發,若是是致使key的cancel後運行極可能沒有效果

2.與其說第一點使得NIO空轉出現的概率大大下降,通過Jetty服務器的測試報告發現,這種重複利用Selector並清空SelectionKey的改法極可能沒有任何的效果,

最終的終極辦法是建立一個新的Selector:

具體的Jetty服務器的分析地址爲:

Jetty/Feature/JVM NIO Bug

Jetty首先定義兩了-D參數:

  • org.mortbay.io.nio.JVMBUG_THRESHHOLD, defaults to 512 and is the number of zero select returns that must be exceeded in a period.

  • org.mortbay.io.nio.MONITOR_PERIOD defaults to 1000 and is the period over which the threshhold applies.

第一個參數是select返回值爲0的計數,第二個是多長時間,總體意思就是控制在多長時間內,若是Selector.select不斷返回0,說明進入了JVM的bug的模式

那麼,Jetty這時候就有所做爲了,咱們看到Jetty的具體的代碼以下:

首先,根據-D參數判斷是否進入了JAVA NIO空轉的bug模式,一個是判斷時間,一個是判斷次數,次數經過-jvmBug做爲計數器進行統計;若是一旦肯定是bug,能夠看到上述代碼爲了防止併發出現,加了Sychronized鎖,接着開啓一個新的Selector,並將原有的SelectionKey的事件所有轉移到了新的Selector中,最後將-jvmBug計數器置0;

==》這種處理方法要保險的多,基本上不會有任何的問題了,

Jetty在這個網頁中還提供了不少參數,如:

即便上述的處理方式,對應極少的linux環境和JDK的版本,仍會出現一些問題,這主要是由於網絡中斷的間隔時間過短形成的,須要給內核必定的時鐘週期進行緩衝,而上述的Jetty的org.mortbay.io.nio.BUSY_PAUSE這個參數就是起到間隔的做用,間隔多少微秒再調用Select,這樣基本上能最大程度上避免上述問題出現了。

從上面Jetty各類處理方法來看,基本能屏蔽低版本JDK和操做系統的epoll的影響,讓NIO能夠無憂運行。固然,對於NIO框架也是修正了這些錯誤,前面提到的Griizzly和Netty都對這個問題採起了響應的策略。

以Netty爲例,具體位置在NioSelector的實現類AbsNioSelector中:

上述的思路和Jetty的處理方式幾乎是同樣的,就是netty講重建Selector的過程抽取成了一個方法,叫作rebuildSelector,能夠看看其方法:

基本上相似,這裏就再也不綴餘。

分析到這裏,能夠看到爲何NIO框架如Netty,Grizzly,還有最近的炒得很熱的Jboss的UnderTow,NIO遠遠不止這篇文章分析得這一個,還有不少,大可在JDK官網上去查,而這些框架都將NIO的不少很差用的問題,bug隱藏起來了,並加上諸如限流,字符轉換,基於設計模式等特性,讓開發人員更好的編寫高併發的程序,而不用過多的網絡的關注與細節。

因而可知,如今JAVA真是愈來愈危機了,從前幾年的SSH把java ee給替換掉,到如今jdk都時不時冒出一個bug來,並且最近JDK8中的一個bug大有超過這個bug之勢,jcp社區確實須要好好檢討了,要否則java沒落了,一干程序員又得下崗再就業了。

總結:

NIO的空轉bug歷史悠久流傳普遍,應用服務器的前端框架通常都採起換一個新Selector的方式對此進行處理,屏蔽掉了JDK5/6的問題,但對於此問題來說,仍是儘可能將JDK的版本更新到最新,或者使用NIO框架如Netty,Grizzly等進行研發,以避免出更多的問題。

 epoll bug CPU空輪詢

SUN在解決該BUG的問題上不給力,只能從NIO框架層面進行問題規避,下面咱們看下Netty是如何解決該問題的。

Netty的解決策略:

1) 根據該BUG的特徵,首先偵測該BUG是否發生;

2) 將問題Selector上註冊的Channel轉移到新建的Selector上;

3) 老的問題Selector關閉,使用新建的Selector替換。

下面具體看下代碼,首先檢測是否發生了該BUG:

圖2-27 epoll bug 檢測

一旦檢測發生該BUG,則重建Selector,代碼以下:

圖2-28 重建Selector

重建完成以後,替換老的Selector,代碼以下:

圖2-29 替換Selector

大量生產系統的運行代表,Netty的規避策略能夠解決epoll bug 致使的IO線程CPU死循環問題。

netty的解決代碼在package io.netty.channel.nio.nioEventLoop這個類下面。

相關文章
相關標籤/搜索