1. 程式人生 > >Java nio的一個嚴重BUG

Java nio的一個嚴重BUG

 

這個BUG會在linux上導致cpu 100%,使得nio server/client不可用,具體的詳情可以看這裡。令人失望的是這個BUG直到jdk 6u4才解決,sun的拖沓讓人難以相信。這個BUGserver端容易出現,因為server端有頻繁地接入斷開連線。使用jdk 6u4之前版本的nio框架都有這個隱患,除非你的框架很好地處理了這個可能的隱患。Grizzly的處理方式比較簡單,也就是BUG報告裡面提到的方式,在SelectionKey.cancel()之後馬上進行了一次select呼叫將fdpoll(epoll)中移除:

Java程式碼 複製程式碼 收藏程式碼
  1. this.selectionKey.cancel();   
  2. try {   
  3. // cancel key,then select now to remove file descriptor
  4. this.selector.selectNow();   
  5.  } catch (IOException e) {   
  6.          onException(e);   
  7.         log.error("Selector selectNow fail", e);   
  8. }  
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

的有效channel和對應的key轉移到新的Selector上。

Java程式碼 複製程式碼 收藏程式碼
  1. long before=now;   
  2. int selected=selector.select(wait);   
  3.                     now = System.currentTimeMillis();   
  4.                     _idleTimeout.setNow(now);   
  5.                     _timeout.setNow(now);   
  6. // Look for JVM bugs
  7. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933
  8. if (__JVMBUG_THRESHHOLD>0 && selected==0 && wait>__JVMBUG_THRESHHOLD && (now-before)<(wait/2) )   
  9.                     {   
  10.                         _jvmBug++;   
  11. if (_jvmBug>=(__JVMBUG_THRESHHOLD2))   
  12.                         {   
  13. synchronized (this)   
  14.                             {   
  15.                                 _lastJVMBug=now;// BLOODY SUN BUG !!!  Try refreshing the entire selector.
  16. final Selector new_selector = Selector.open();   
  17. for (SelectionKey k: selector.keys())   
  18.                                 {   
  19. if (!k.isValid() || k.interestOps()==0)   
  20. continue;   
  21. final SelectableChannel channel = k.channel();   
  22. final Object attachment = k.attachment();   
  23. if (attachment==null)   
  24.                                         addChange(channel);   
  25. else
  26.                                         addChange(channel,attachment);   
  27.                                 }   
  28.                                 _selector.close();   
  29.                                 _selector=new_selector;   
  30.                                 _jvmBug=0;   
  31. return;   
  32.                             }   
  33.                         }   
  34. elseif (_jvmBug==__JVMBUG_THRESHHOLD || _jvmBug==__JVMBUG_THRESHHOLD1)   
  35.                         {   
  36. // Cancel keys with 0 interested ops
  37. for (SelectionKey k: selector.keys())   
  38.                             {   
  39. if (k.isValid()&&k.interestOps()==0)   
  40.                                 {   
  41.                                     k.cancel();   
  42.                                 }   
  43.                             }   
  44. return;   
  45.                         }   
  46.                     }   
  47. else
  48.                         _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可能導致的問題。MinaNetty沒有看到有處理這個BUG的程式碼,如果我看錯了,請留言告訴我。Yanf4j一直採用的是grizzly的方式,準備加上jetty的處理方案。當然,最簡單的方案就是升級你的JDK。

俺實際上被這個BUG害的很慘, 因為降到了jdk 6u4 CPU仍然會時不時的增高, 原因是低版本的Jetty同樣在這方面處理的不是很合理. Jetty關於這個BUG的描述JETTY做為應用, JETTY1.6.22 + JDK 6U4俺現在測下來, CPU基本正常.