转载 

java NIO的空轮询bug 以及Netty的解决办法_nio bug_Gogym的博客

分类:    387人阅读    IT小君  2023-09-07 23:22

这个bug是指 java的NIO在linux下selector.select()时,本来如果轮询的结果为空并且不调用wakeup的方法的话,这个selector.select()应该是一直阻塞的,但是java却会打破阻塞,继续执行,导致程序无限空转,造成CPU使用率100%
java NIO的空轮询bug 以及Netty的解决办法_第1张图片
这个bug只出现在linux系统下,因为linux下NIO底层使用的是epoll来实现的,而java的epoll实现存在bug,导致selector出现了这种轮询为空却唤醒的情况。windows下NIO是使用的poll来实现selector的就不存在这种bug

Netty中解决该bug的方法

1、设置一个selector.select(timeout),有一个超时时间,selector有4种情况会跳出阻塞

  1. 有事件发生
  2. wakeup
  3. 超时
  4. 空轮询bug

而前两种返回值不为0,可以跳出循环,超时有时间戳记录,所以每次空轮询,有专门 的计数器+1,如果空轮询的次数超过了512次,就认为其触发了空轮询bug。

2、触发bug后,netty直接重建一个selector,将原来的channel重新注册到新的selector上,将旧的 selector关掉

  1. private void select(boolean oldWakenUp) throws IOException {//节选
  2. for(;;)
  3. int selectedKeys = selector.select(timeoutMillis);
  4. selectCnt ++;
  5. if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
  6. // - Selected something,
  7. // - waken up by user, or
  8. // - the task queue has a pending task.
  9. // - a scheduled task is ready for processing
  10. break;
  11. }
  12. long time = System.nanoTime();
  13. if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
  14. // timeoutMillis elapsed without anything selected.
  15. // 超时
  16. selectCnt = 1;
  17. } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
  18. selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {//默认值512
  19. // The code exists in an extra method to ensure the method is not too big to inline as this
  20. // branch is not very likely to get hit very frequently.
  21. // 空轮询一次 cnt+1 如果一个周期内次数超过512,则假定发生了空轮询bug,重建selector
  22. selector = selectRebuildSelector(selectCnt);
  23. selectCnt = 1;
  24. break;
  25. }
  26. }
  27. }
  28. /**
  29. * Replaces the current {@link Selector} of this event loop with newly created {@link Selector}s to work
  30. * around the infamous epoll 100% CPU bug.
  31. * 新建一个selector来解决空轮询bug
  32. */
  33. public void rebuildSelector() {
  34. if (!inEventLoop()) {
  35. execute(new Runnable() {
  36. @Override
  37. public void run() {
  38. rebuildSelector0();
  39. }
  40. });
  41. return;
  42. }
  43. rebuildSelector0();
  44. }
  45. private void rebuildSelector0() {
  46. final Selector oldSelector = selector;
  47. final SelectorTuple newSelectorTuple;
  48. //新建一个selector
  49. newSelectorTuple = openSelector();
  50. // 将旧的selector的channel全部拿出来注册到新的selector上
  51. int nChannels = 0;
  52. for (SelectionKey key: oldSelector.keys()) {
  53. Object a = key.attachment();
  54. if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
  55. continue;
  56. }
  57. int interestOps = key.interestOps();
  58. key.cancel();
  59. SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);
  60. if (a instanceof AbstractNioChannel) {
  61. // Update SelectionKey
  62. ((AbstractNioChannel) a).selectionKey = newKey;
  63. }
  64. nChannels ++;
  65. }
  66. selector = newSelectorTuple.selector;
  67. unwrappedSelector = newSelectorTuple.unwrappedSelector;
  68. // time to close the old selector as everything else is registered to the new one
  69. //关掉旧的selector
  70. oldSelector.close();
  71. }

转载于:https://blog.csdn.net/KokJuis/article/details/113318641

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者

 工具推荐 更多»