Java nio的一個嚴重BUG
這個BUG會在linux上導致cpu 100%,使得nio server/client不可用,具體的詳情可以看這裡。令人失望的是這個BUG直到jdk 6u4才解決,sun的拖沓讓人難以相信。這個BUG在server端容易出現,因為server端有頻繁地接入斷開連線。使用jdk 6u4之前版本的nio框架都有這個隱患,除非你的框架很好地處理了這個可能的隱患。Grizzly的處理方式比較簡單,也就是BUG報告裡面提到的方式,在SelectionKey.cancel()之後馬上進行了一次select呼叫將fd從poll(epoll)中移除:
Java程式碼- this.selectionKey.cancel();
- try {
- // cancel key,then select now to remove file descriptor
- this.selector.selectNow();
- } catch (IOException e) {
- onException(e);
- log.error("Selector selectNow fail", e);
- }
this.selectionKey.cancel(); try { // cancel key,then select now to remove file descriptor this.selector.selectNow(); } catch (IOException e) { onException(e); log.error("Selector selectNow fail", e); }
實際上這樣的解決方式還是留有隱患的,因為key的取消和這個selectNow操作很可能跟Selector.select操作併發地在進行,在兩個操作之間仍然留有一個極小的時間視窗可能發生這個BUG。因此,你需要更安全地方式處理這個問題,jetty的處理方式是這樣,連續的 select(timeout)操作沒有阻塞並返回0,並且次數超過了一個指定閥值,那麼就遍歷整個key set,將key仍然有效並且interestOps等於0的所有key主動取消掉;如果在這次修正後,仍然繼續出現select(timeout)不阻塞並且返回0的情況,那麼就重新建立一個新的Selector,並將Old Selector
- long before=now;
- int selected=selector.select(wait);
- now = System.currentTimeMillis();
- _idleTimeout.setNow(now);
- _timeout.setNow(now);
- // Look for JVM bugs
- // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933
- if (__JVMBUG_THRESHHOLD>0 && selected==0 && wait>__JVMBUG_THRESHHOLD && (now-before)<(wait/2) )
- {
- _jvmBug++;
- if (_jvmBug>=(__JVMBUG_THRESHHOLD2))
- {
- synchronized (this)
- {
- _lastJVMBug=now;// BLOODY SUN BUG !!! Try refreshing the entire selector.
- final Selector new_selector = Selector.open();
- for (SelectionKey k: selector.keys())
- {
- if (!k.isValid() || k.interestOps()==0)
- continue;
- final SelectableChannel channel = k.channel();
- final Object attachment = k.attachment();
- if (attachment==null)
- addChange(channel);
- else
- addChange(channel,attachment);
- }
- _selector.close();
- _selector=new_selector;
- _jvmBug=0;
- return;
- }
- }
- elseif (_jvmBug==__JVMBUG_THRESHHOLD || _jvmBug==__JVMBUG_THRESHHOLD1)
- {
- // Cancel keys with 0 interested ops
- for (SelectionKey k: selector.keys())
- {
- if (k.isValid()&&k.interestOps()==0)
- {
- k.cancel();
- }
- }
- return;
- }
- }
- else
- _jvmBug=0;
long before=now;
int selected=selector.select(wait);
now = System.currentTimeMillis();
_idleTimeout.setNow(now);
_timeout.setNow(now);
// Look for JVM bugs
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933
if (__JVMBUG_THRESHHOLD>0 && selected==0 && wait>__JVMBUG_THRESHHOLD && (now-before)<(wait/2) )
{
_jvmBug++;
if (_jvmBug>=(__JVMBUG_THRESHHOLD2))
{
synchronized (this)
{
_lastJVMBug=now;// BLOODY SUN BUG !!! Try refreshing the entire selector.
final Selector new_selector = Selector.open();
for (SelectionKey k: selector.keys())
{
if (!k.isValid() || k.interestOps()==0)
continue;
final SelectableChannel channel = k.channel();
final Object attachment = k.attachment();
if (attachment==null)
addChange(channel);
else
addChange(channel,attachment);
}
_selector.close();
_selector=new_selector;
_jvmBug=0;
return;
}
}
else if (_jvmBug==__JVMBUG_THRESHHOLD || _jvmBug==__JVMBUG_THRESHHOLD1)
{
// Cancel keys with 0 interested ops
for (SelectionKey k: selector.keys())
{
if (k.isValid()&&k.interestOps()==0)
{
k.cancel();
}
}
return;
}
}
else
_jvmBug=0;
這個方案能比較好的在jdk 6u4之前的版本上解決這個BUG可能導致的問題。Mina和Netty沒有看到有處理這個BUG的程式碼,如果我看錯了,請留言告訴我。Yanf4j一直採用的是grizzly的方式,準備加上jetty的處理方案。當然,最簡單的方案就是升級你的JDK。
俺實際上被這個BUG害的很慘, 因為降到了jdk 6u4 CPU仍然會時不時的增高, 原因是低版本的Jetty同樣在這方面處理的不是很合理. Jetty關於這個BUG的描述: 用JETTY做為應用, JETTY1.6.22 + JDK 6U4俺現在測下來, CPU基本正常.