Netty源码分析---Epoll空轮询Bug深度解析

面试题趣谈:Netty如何处理NIO底层epoll空轮询bug? 你了解Netty的NIO epoll空轮询bug问题吗? 能否解释一下这个问题的成因和Netty是如何解决的?这个问题的成因是NIO底层epoll实现存在缺陷,它会频繁空轮询所有连接,即使没有新事件发生也会遍历所有连接。这会导致CPU使用率飙升到100%。Netty对这个问题进行了识别和处理。当它检测到Selector出现空轮询时,会立即创建一个新的Selector,然后将旧Selector上的SocketChannel重新注册到新Selector,最后关闭旧的Selector释放资源。 阅读详情

Epoll 空轮询 Bug 深度解析

一、问题描述

1.1 什么是 Epoll 空轮询 Bug?

这是 JDK NIO 在 Linux 平台上的一个严重 Bug,表现为:

  • Selector.select() 方法应该阻塞等待事件,但却立即返回
  • 返回值为 0(没有任何事件就绪)
  • 导致 while 循环不断空转,CPU 使用率飙升到 100%
  • 系统完全不可用

官方 Bug 报告:

  • Bug ID: 6403933
  • Bug ID: 6670302
  • 状态:至今未完全修复(JDK 17 仍存在)

1.2 为什么只在 Linux 下出现?

因为不同操作系统的 NIO 底层实现不同:

操作系统NIO 底层实现是否有 Bug
Linuxepoll✓ 有
WindowsIOCP✗ 无
macOSkqueue✗ 无

Linux 下 JDK 的 epoll 实现存在缺陷,导致了这个 Bug。


二、JDK NIO 的正常工作流程

2.1 正常的 NIO 服务器代码

// 正常的 NIO 服务器
public class NioServer {
    public static void main(String[] args) throws IOException {
        // 1. 创建 Selector
        Selector selector = Selector.open();
        
        // 2. 创建 ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new InetSocketAddress(8080));
        
        // 3. 注册到 Selector
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        System.out.println("服务器启动,等待连接...");
        
        // 4. 事件循环
        while (true) {
            // 阻塞等待事件,正常情况下会一直阻塞
            // 直到有事件发生或者超时
            int readyChannels = selector.select();  // 关键点!
            
            if (readyChannels == 0) {
                // 正常情况下,只有超时或被 wakeup() 才会返回 0
                continue;
            }
            
            // 处理事件
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
            
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                
                if (key.isAcceptable()) {
                    // 处理连接事件
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                    System.out.println("接受新连接: " + client.getRemoteAddress());
                }
                else if (key.isReadable()) {
                    // 处理读事件
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len = client.read(buffer);
                    if (len > 0) {
                        System.out.println("读取数据: " + new String(buffer.array(), 0, len));
                    }
                }
            }
        }
    }
}

2.2 正常情况下的执行流程

1. selector.select() 被调用
   ↓
2. 线程阻塞,等待事件
   ↓
3. 有事件发生(例如客户端连接)
   ↓
4. selector.select() 返回,返回值 > 0
   ↓
5. 处理事件
   ↓
6. 回到步骤 1

关键点:selector.select() 应该阻塞,直到有事件发生才返回。


三、Epoll Bug 的异常表现

3.1 Bug 触发时的代码执行

while (true) {
    // Bug 触发:select() 立即返回,返回值为 0
    int readyChannels = selector.select();  // 立即返回 0!
    
    if (readyChannels == 0) {
        System.err.println("空轮询!没有事件但 select() 返回了");
        // 继续循环,又立即返回 0
        // 无限循环,CPU 100%
        continue;
    }
    
    // 这段代码永远不会执行,因为 readyChannels 一直是 0
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    // ...
}

3.2 Bug 触发时的执行流程

1. selector.select() 被调用
   ↓
2. 应该阻塞,但立即返回(Bug!)
   ↓
3. 返回值 = 0(没有任何事件)
   ↓
4. selectedKeys 为空,不处理任何事件
   ↓
5. 回到步骤 1(立即执行,不阻塞)
   ↓
6. 无限循环,CPU 100%

问题:selector.select() 没有阻塞,立即返回 0,导致 while 循环空转。

3.3 实际日志示例

服务器启动,等待连接...
空轮询!没有事件但 select() 返回了
空轮询!没有事件但 select() 返回了
空轮询!没有事件但 select() 返回了
空轮询!没有事件但 select() 返回了
... (无限重复,CPU 100%)

四、Bug 的根本原因

4.1 Linux Epoll 机制简介

在 Linux 下,JDK NIO 的 Selector 底层使用 epoll 系统调用实现。

Epoll 的三个核心函数:

// 1. 创建 epoll 实例
int epfd = epoll_create(int size);

// 2. 注册/修改/删除文件描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

// 3. 等待事件(对应 Selector.select())
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

JDK NIO 与 Epoll 的对应关系:

Selector.open()     →  epoll_create()
channel.register()  →  epoll_ctl(EPOLL_CTL_ADD)
selector.select()   →  epoll_wait()

4.2 Bug 的触发条件

根据 JDK Bug 报告和社区分析,Bug 主要在以下情况触发:

情况 1:连接被远程关闭

1. 客户端连接到服务器
2. 服务器将 SocketChannel 注册到 Selector,监听 OP_READ
3. 客户端异常断开(发送 RST 或 FIN)
4. Linux 内核的 epoll 错误地认为该 fd 仍然有事件
5. epoll_wait() 立即返回,但实际上没有可读数据
6. JDK 的 Selector.select() 也立即返回 0
7. 无限循环

情况 2:文件描述符状态异常

1. 某个 Channel 的文件描述符进入异常状态
2. epoll 无法正确处理这个异常状态
3. epoll_wait() 认为有事件,但实际上没有
4. 导致空轮询

4.3 JDK 源码分析

JDK 的 EPollSelectorImpl.java(简化版):

// sun.nio.ch.EPollSelectorImpl
protected int doSelect(long timeout) throws IOException {
    if (closed)
        throw new ClosedSelectorException();
    
    processDeregisterQueue();
    
    try {
        begin();
        
        // 调用 native 方法,最终调用 epoll_wait()
        pollWrapper.poll(timeout);
        
    } finally {
        end();
    }
    
    processDeregisterQueue();
    
    // 更新 selectedKeys
    int numKeysUpdated = updateSelectedKeys();
    
    // 如果 pollWrapper.interrupted() 为 true,返回 0
    if (pollWrapper.interrupted()) {
        pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
        synchronized (interruptLock) {
            pollWrapper.clearInterrupted();
            IOUtil.drain(fd0);
            interruptTriggered = false;
        }
        return 0;
    }
    
    return numKeysUpdated;
}

Native 层的 epoll_wait 调用:

// EPollArrayWrapper.c
JNIEXPORT jint JNICALL
Java_sun_nio_ch_EPollArrayWrapper_epollWait(JNIEnv *env, jobject this,
                                             jlong address, jint numfds,
                                             jlong timeout)
{
    struct epoll_event *events = jlong_to_ptr(address);
    int res;

    // 调用 Linux 的 epoll_wait 系统调用
    if (timeout <= 0) {
        res = epoll_wait(epfd, events, numfds, timeout);
    } else {
        res = epoll_wait(epfd, events, numfds, timeout);
    }

    if (res < 0) {
        if (errno == EINTR) {
            return IOS_INTERRUPTED;
        } else {
            JNU_ThrowIOExceptionWithLastError(env, "epoll_wait failed");
            return IOS_THROWN;
        }
    }
    
    return res;  // 返回就绪的文件描述符数量
}

问题所在:

当 Linux 内核的 epoll 实现出现 Bug 时,epoll_wait() 会立即返回,但返回值可能是 0 或者返回的事件实际上无效。JDK 无法判断这是正常的超时返回还是 Bug 导致的异常返回,所以就出现了空轮询。

4.4 为什么 JDK 不修复?

  1. 这是 Linux 内核的 Bug:问题根源在 Linux 内核的 epoll 实现,不是 JDK 的问题
  2. 难以复现:Bug 触发条件复杂,不是每次都能复现
  3. 影响范围有限:只在特定的 Linux 内核版本和特定场景下触发
  4. 修复成本高:需要在 JDK 层面做大量的兼容性处理

五、Netty 的解决方案

Netty 采用了一种巧妙的方式来规避这个 Bug:检测空轮询次数,超过阈值就重建 Selector。

5.1 核心思路

1. 记录每次 select() 的返回情况
2. 如果连续多次空轮询(返回 0 且没有超时),计数器 +1
3. 如果计数器超过阈值(默认 512),认为触发了 Bug
4. 重建 Selector,将所有 Channel 重新注册
5. 关闭旧的 Selector

5.2 Netty 源码分析

NioEventLoop.java 的 select() 方法:

// io.netty.channel.nio.NioEventLoop
private void select(boolean oldWakenUp) throws IOException {
    Selector selector = this.selector;
    
    try {
        int selectCnt = 0;  // 空轮询计数器
        long currentTimeNanos = System.nanoTime();
        
        // 计算超时时间
        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
        
        for (;;) {
            // 计算本次 select 的超时时间
            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
            
            if (timeoutMillis <= 0) {
                // 已经超时,执行一次非阻塞的 selectNow()
                if (selectCnt == 0) {
                    selector.selectNow();
                    selectCnt = 1;
                }
                break;
            }

            // 如果有任务要执行,也执行非阻塞的 selectNow()
            if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                selector.selectNow();
                selectCnt = 1;
                break;
            }

            // 执行阻塞的 select(),这里可能触发 Bug
            int selectedKeys = selector.select(timeoutMillis);
            selectCnt++;  // 每次 select 后计数器 +1

            // 判断是否应该跳出循环
            if (selectedKeys != 0 ||           // 有事件
                oldWakenUp ||                  // 被 wakeup
                wakenUp.get() ||               // 被 wakeup
                hasTasks() ||                  // 有任务
                hasScheduledTasks()) {         // 有定时任务
                
                // 正常情况,跳出循环
                break;
            }
            
            // 如果线程被中断,跳出循环
            if (Thread.interrupted()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Selector.select() returned prematurely because " +
                            "Thread.currentThread().interrupt() was called. Use " +
                            "NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
                }
                selectCnt = 1;
                break;
            }

            // 关键:检查是否触发了空轮询 Bug
            long time = System.nanoTime();
            if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                // 说明确实超时了,这是正常的
                // 重置计数器
                selectCnt = 1;
            } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                    selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                
                // 空轮询次数超过阈值(默认 512)
                // 认为触发了 epoll bug,重建 Selector
                logger.warn(
                    "Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
                    selectCnt, selector);

                rebuildSelector();  // 重建 Selector
                selector = this.selector;

                // 重建后,重置计数器
                selectCnt = 1;
                break;
            }

            currentTimeNanos = time;
        }

        if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
            if (logger.isDebugEnabled()) {
                logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                        selectCnt - 1, selector);
            }
        }
    } catch (CancelledKeyException e) {
        if (logger.isDebugEnabled()) {
            logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                    selector, e);
        }
    }
}

5.3 关键判断逻辑详解

判断是否是真正的超时:

long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
    // 说明确实超时了,这是正常的
    selectCnt = 1;
}

逻辑解释:

假设:
- currentTimeNanos = 1000(select 开始时间)
- timeoutMillis = 100(超时时间 100ms)
- time = 1150(select 返回时间)

判断:
time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos
1150 - 100 >= 1000
1050 >= 1000  ✓

说明:确实等待了 100ms,这是正常的超时返回

判断是否触发了 Bug:

else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
         selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
    // 触发了 Bug,重建 Selector
    rebuildSelector();
}

逻辑解释:

如果:
- 不是正常超时(上面的 if 不成立)
- 空轮询次数 >= 512

说明:
- select() 立即返回(没有等待超时时间)
- 连续发生了 512 次
- 很可能触发了 epoll bug

解决:
- 重建 Selector

5.4 重建 Selector 的详细过程

rebuildSelector() 方法:

// io.netty.channel.nio.NioEventLoop
public void rebuildSelector() {
    if (!inEventLoop()) {
        // 如果不在 EventLoop 线程中,提交任务
        execute(new Runnable() {
            @Override
            public void run() {
                rebuildSelector0();
            }
        });
        return;
    }
    rebuildSelector0();
}

private void rebuildSelector0() {
    final Selector oldSelector = selector;
    final SelectorTuple newSelectorTuple;

    if (oldSelector == null) {
        return;
    }

    try {
        // 1. 创建新的 Selector
        newSelectorTuple = openSelector();
    } catch (Exception e) {
        logger.warn("Failed to create a new Selector.", e);
        return;
    }

    // 2. 将所有 Channel 从旧 Selector 迁移到新 Selector
    int nChannels = 0;
    for (SelectionKey key: oldSelector.keys()) {
        Object a = key.attachment();
        
        try {
            // 检查 key 是否有效
            if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
                continue;
            }

            // 获取感兴趣的事件
            int interestOps = key.interestOps();
            
            // 取消旧的注册
            key.cancel();
            
            // 注册到新的 Selector
            SelectionKey newKey = key.channel().register(
                newSelectorTuple.unwrappedSelector, 
                interestOps, 
                a
            );
            
            // 更新 Channel 中的 SelectionKey 引用
            if (a instanceof AbstractNioChannel) {
                ((AbstractNioChannel) a).selectionKey = newKey;
            }
            
            nChannels++;
        } catch (Exception e) {
            logger.warn("Failed to re-register a Channel to the new Selector.", e);
            if (a instanceof AbstractNioChannel) {
                AbstractNioChannel ch = (AbstractNioChannel) a;
                ch.unsafe().close(ch.unsafe().voidPromise());
            } else {
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                invokeChannelUnregistered(task, key, e);
            }
        }
    }

    // 3. 更新 Selector 引用
    selector = newSelectorTuple.selector;
    unwrappedSelector = newSelectorTuple.unwrappedSelector;

    try {
        // 4. 关闭旧的 Selector
        oldSelector.close();
    } catch (Throwable t) {
        if (logger.isWarnEnabled()) {
            logger.warn("Failed to close the old Selector.", t);
        }
    }

    if (logger.isInfoEnabled()) {
        logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");
    }
}

5.5 重建流程图解

触发 Bug 前:
┌─────────────────────────────────────┐
│         旧 Selector                  │
│  ┌──────────┐  ┌──────────┐        │
│  │Channel 1 │  │Channel 2 │  ...   │
│  └──────────┘  └──────────┘        │
└─────────────────────────────────────┘
         ↓ 触发 Bug(空轮询 512 次)

重建过程:
1. 创建新 Selector
┌─────────────────────────────────────┐
│         新 Selector                  │
│  (空的)                            │
└─────────────────────────────────────┘

2. 迁移 Channel
┌─────────────────────────────────────┐
│         旧 Selector                  │
│  ┌──────────┐  ┌──────────┐        │
│  │Channel 1 │  │Channel 2 │        │
│  └────┬─────┘  └────┬─────┘        │
└───────┼─────────────┼───────────────┘
        │             │
        │ 取消注册    │ 取消注册
        ↓             ↓
┌─────────────────────────────────────┐
│         新 Selector                  │
│  ┌──────────┐  ┌──────────┐        │
│  │Channel 1 │  │Channel 2 │        │
│  └──────────┘  └──────────┘        │
└─────────────────────────────────────┘
        ↑ 重新注册

3. 关闭旧 Selector
┌─────────────────────────────────────┐
│         旧 Selector                  │
│  (已关闭)                          │
└─────────────────────────────────────┘

4. 使用新 Selector
┌─────────────────────────────────────┐
│         新 Selector                  │
│  ┌──────────┐  ┌──────────┐        │
│  │Channel 1 │  │Channel 2 │  ...   │
│  └──────────┘  └──────────┘        │
└─────────────────────────────────────┘
         ↓ 继续正常工作

六、为什么 Netty 的方案有效?

6.1 核心原理

Bug 的本质:旧的 Selector 内部状态异常,导致 epoll_wait() 行为异常。

Netty 的解决:创建一个全新的 Selector,新的 Selector 状态正常,不会触发 Bug。

6.2 为什么选择 512 这个阈值?

// io.netty.channel.nio.NioEventLoop
private static final int SELECTOR_AUTO_REBUILD_THRESHOLD = 
    SystemPropertyUtil.getInt("io.netty.selectorAutoRebuildThreshold", 512);

原因:

  1. 避免误判:偶尔的空轮询可能是正常的(例如被 wakeup),512 次足够排除误判
  2. 及时响应:512 次空轮询在现代 CPU 上只需要几毫秒,不会让 CPU 100% 持续太久
  3. 经验值:Netty 团队经过大量测试得出的经验值

可配置:可以通过系统属性修改:

// 启动时设置
System.setProperty("io.netty.selectorAutoRebuildThreshold", "256");

// 或者在启动参数中设置
java -Dio.netty.selectorAutoRebuildThreshold=256 YourApp

6.3 性能影响

重建 Selector 的成本:

1. 创建新 Selector:约 1ms
2. 迁移 Channel:每个 Channel 约 0.01ms
3. 关闭旧 Selector:约 0.5ms

总计:假设有 1000 个 Channel,约 1 + 10 + 0.5 = 11.5ms

对比 CPU 100% 的影响:

不重建:CPU 100%,系统完全不可用
重建:  短暂的 11.5ms 延迟,之后恢复正常

显然,重建的代价是完全可以接受的。

七、实战演示

7.1 模拟 Bug 触发(理论代码)

// 注意:这只是理论代码,实际很难稳定复现
public class EpollBugDemo {
    public static void main(String[] args) throws Exception {
        Selector selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new InetSocketAddress(8080));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        int emptySelectCount = 0;
        long startTime = System.currentTimeMillis();

        while (true) {
            int ready = selector.select(1000);  // 1 秒超时
            
            if (ready == 0) {
                emptySelectCount++;
                long elapsed = System.currentTimeMillis() - startTime;
                
                if (elapsed < 100) {  // 100ms 内返回 0,可能是 Bug
                    System.err.println("可能触发 Bug!空轮询次数: " + emptySelectCount);
                    
                    if (emptySelectCount > 10) {
                        System.err.println("确认触发 Bug!CPU 即将 100%");
                        break;
                    }
                } else {
                    // 正常超时
                    emptySelectCount = 0;
                }
                
                startTime = System.currentTimeMillis();
                continue;
            }

            // 处理事件
            emptySelectCount = 0;
            startTime = System.currentTimeMillis();
            
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                it.remove();
                // 处理事件...
            }
        }
    }
}

7.2 Netty 的日志输出

当 Netty 检测到 Bug 并重建 Selector 时,会输出日志:

WARN  io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 512 times in a row; rebuilding Selector io.netty.channel.nio.SelectedSelectionKeySetSelector@6edf74c3.
INFO  io.netty.channel.nio.NioEventLoop - Migrated 15 channel(s) to the new Selector.

日志解读:

  • returned prematurely 512 times:连续 512 次空轮询
  • rebuilding Selector:正在重建 Selector
  • Migrated 15 channel(s):成功迁移了 15 个 Channel

八、总结

8.1 Bug 的本质

方面说明
根本原因Linux 内核 epoll 实现的 Bug
表现selector.select() 立即返回 0,不阻塞
后果while 循环空转,CPU 100%
影响范围只在 Linux 平台,特定场景下触发
JDK 状态至今未修复(因为是内核 Bug)

8.2 Netty 的解决方案

方面说明
核心思路检测空轮询次数,超过阈值重建 Selector
阈值默认 512 次(可配置)
重建过程创建新 Selector → 迁移 Channel → 关闭旧 Selector
性能影响约 10ms(取决于 Channel 数量)
效果完全规避 Bug,系统恢复正常

8.3 关键要点

  1. 这是 Linux 内核的 Bug,不是 JDK 或 Netty 的问题
  2. JDK 无法修复,因为问题在内核层面
  3. Netty 巧妙规避,通过重建 Selector 解决
  4. 代价很小,重建只需要几毫秒
  5. 效果显著,完全避免 CPU 100% 的问题

8.4 学习建议

  1. 理解 epoll 的工作原理
  2. 理解 Selector.select() 的阻塞机制
  3. 理解 Netty 的检测和重建逻辑
  4. 在生产环境中关注 Netty 的日志,如果频繁出现重建日志,说明系统可能有问题

参考资料

  1. JDK Bug 报告:
    • https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933
    • https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6670302
  2. Netty Issue:
    • https://github.com/netty/netty/issues/327
    • https://github.com/netty/netty/issues/8698
  3. Linux epoll 文档:
    • man epoll
    • https://man7.org/linux/man-pages/man7/epoll.7.html
电池充电管理芯片规格书精读指南:从参数解析到设计实战 电池充电管理是嵌入式系统和便携式设备开发中的基础环节,其核心在于理解并应用充电芯片的规格参数。充电芯片通过精确控制电压、电流、温度等关键参数,实现对锂电池等可充电电池的安全、高效管理。掌握这些参数原理,对于优化产品功耗、提升充电效率、保障电池寿命具有重要技术价值,广泛应用于智能穿戴、消费电子、无人机等场景。本文聚焦于**充电电流**设定与**热管理**等核心参数,深入解析其相互影响与工程选型中的权衡,旨在帮助开发者将抽象的规格书数据转化为可靠的电路设计依据,从而有效排查充电故障,提升产品整体可靠性。 阅读详情

相关推荐

三维点云孔洞修复(附Python代码和公共点云数据集链接)

(1)物体自身缺失(2)物体自身形状复杂,很多凹凸面不够规则甚至存在空腔,空腔内部无法被三维激光扫描仪扫描到(3)物体部分区域对激光扫描不敏感,未被扫描上(4)扫描仪的扫描稳定性。

yanghl1215的博客 6954

Netty巧妙的规避了JDK在linux中NIO的epoll的空轮询Bug

一、NIO的空轮询bug描述 JDK1.5开始引入了epoll基于事件响应机制来优化NIO。相较于select和poll机制来说,epoll机制将事件处理交给了操作系统内核(操作系统硬中断)来处理,优化了elect和poll模型的无效遍历问题。 但是JDK中epoll的实现却是有漏洞的,其中最有名的就是NIO空轮询bug(该bug只存在于Linux,因为Linux中NIO底层是使用epoll实现的,而Windows不是)。 理论上无客户端连接时Selector.select() 方法会阻塞,但空轮询b.

HD243608836的博客 1904

【抓包工具】Windows 10/11:Charles 从零到精通(下载、安装、配置与HTTPS证书实战)

本文提供了一份详尽的Charles抓包工具在Windows 10/11系统上的从零到精通指南。内容涵盖下载安装、初始配置、解决端口冲突,并重点解析了HTTPS抓包的核心步骤——安装CA证书。此外,还介绍了手机抓包配置及映射、断点等高效使用技巧,帮助开发者和测试人员快速掌握网络调试与安全分析技能。

weixin_29251131的博客 552

JDK Epoll空轮询bug

前面讲到了epoll的一些机制,与select和poll等传统古老的IO多路复用机制的一些区别,这些区别实质可以总结为一句话,就是epoll将重要的基于事件的fd集合放在了内核中来完成,因为内核是高效的,所以很多关于fd事件监听集合的操作也是高效的,不方便的就是,因为在内核中,所以我们需要通过系统调用来调用关于fd操作集合,而不是直接自己攒一个。如果在linux中,epoll在JDK6中还需要配置,在后续的版本中为JDK的NIO提供了默认的实现,但是epoll在JDK中的实现却是漏洞百出的,bug非常的多,

傅红雪的专栏 1064

Outlook日历同步全攻略:Win10+安卓+iOS三端无缝衔接(附常见问题解决)

本文提供了一份详尽的Outlook日历同步指南,帮助用户在Windows 10、安卓和iOS三端实现无缝衔接。文章深入解析了Exchange ActiveSync等核心同步原理,并给出了从账户创建到各设备(特别是华为手机)的具体配置步骤、进阶管理技巧以及针对同步失败、事件重复等常见问题的实战解决方案,旨在彻底解决跨平台日程管理的痛点。

l3m4n的博客 556

微信小程序 - 最新实现项目适老化设计全流程及源码,老年模式大字版与常规版页面样式切换,老年人大字模式字体与布局更换关怀版解决方案(新老项目均适用的方案,全局页面统一修改文本字号/DOM排版元素等)

微信小程序,vue2,vue3,老年大字版设计,小程序开发如何实现适老化方案,放大字体字号及改变页面的布局,小程序关怀模式实现思路及代码,统一将布局都改了,全局修改字体大小适配老年人看不清问题,小程序怎么接入老年模式并根据模式改变页面的UI设计,实现全局修改字体大小适老化/关怀版改造,最新详细示例代码全流程,字体放大/缩小,老项目怎么适配老年人模式方案,小程序适老化设计指南,当切换成老年模式时页面所有排版都会发生变化,小程序vue开发,新手详细教程,老年模式字体设置思路和UI布局的设计,开发者可以通过wx.

🏆 前端领域优质创作者 7309

Java NIO中的 epoll bug分析与复现

Java NIO中的epoll bug 文章目录Java NIO中的epoll bug

speakguoer的博客 2039

在zcu102开发板上使用PetaLinux运行Qt程序

请注意,上述步骤只是一个概述,并且可能需要根据特定的开发环境和要求进行适当的调整。如果你是一个初学者或者对于这些领域不太熟悉,建议在尝试之前阅读相关的文档和教程,或者寻求专业人士的帮助。在"Projects"菜单中,选择"Build & Run"选项,并在"Build"步骤中选择正确的工具链。注意:如果Qt程序依赖于其他库文件,需要将这些库文件一起复制到PetaLinux工程的根文件系统目录中,并在Qt程序中正确地配置库文件路径。如果一切顺利,PetaLinux系统将启动,并自动运行Qt程序。

UhxzCombined的博客 698

面试难题:Netty如何解决Selector空轮询BUG?(图解+秒懂+史上最全)

文章很长,建议收藏起来,慢慢读! Java 高并发 发烧友社群:疯狂创客圈 奉上以下珍贵的学习资源: 免费赠送 经典图书:《Java高并发核心编程(卷1)》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领 免费赠送 经典图书:《Java高并发核心编程(卷2)》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领 免费赠送 经典图书:《Netty Zookeeper Redis 高并发实战》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领 免费赠送 经典图书:《SpringCloud

架构师尼恩 2972

大数据面试必考题:Spark数据倾斜问题总结和优化措施

在Spark分布式计算环境中,

大模型大数据攻城狮的专栏 1466

使用Python爬虫抓取政府网站政务信息:公告、通知的自动化抓取与分析

通过结合Requests、BeautifulSoup、Selenium等技术,你能够高效地抓取政府网站的数据,并通过MySQL存储与Pandas分析这些数据。通过自动化手段抓取这些信息,不仅可以提高数据收集的效率,还能够实时获取最新的政务动态,为政策研究、舆情分析等提供宝贵数据。我们将通过Python的Requests、BeautifulSoup、Selenium等工具,结合最新的技术实现一个完整的爬虫系统,帮助你轻松抓取并分析政务信息。通过爬虫,我们可以自动化地从指定网站上抓取公告、新闻、通知等信息。

2201_76125261的博客 1907

NIO中存在的bug—epoll空轮询

IO&NIO介绍 IO读取 NIO读取 NIO中epoll空轮询表现 public static void main(String[] args) { Selector selector = Selector.open(); System.out.println(selector.isOpen()); ServerS...

小弟_liao的博客 788

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

java epoll bug_NIO的epoll空轮询bug

JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有被根本解决。该BUG以及与该BUG相关的问题单可以参见以下链接内容。Selector BUG出现的原因若Selector的轮询结果为空,也没...

weixin_31776191的博客 2046

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

netty的boss线程组原理

唯一绑定端口的线程:Boss 线程组中第一个活跃的线程(记为 Boss-1 )通过 ServerSocketChannel 监听端口,接收客户端的连接请求( OP_ACCEPT 事件)。- 连接建立完成:当 Boss-1 接收到连接请求后,会创建对应的 SocketChannel (代表客户端连接),并将其分配给 Boss 线程组中的某个线程处理后续初始化。- 主 Boss 线程负责接收连接,其他 Boss 线程通过轮询分配连接初始化任务,分工协作提升高并发场景下的连接处理效率。

lzf的博客 1307

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

epoll 空轮询以及netty的解决方式

什么是 epoll 空轮询 如果使用 Java 原生 NIO 来编写服务器应用,代码一般类似: // 创建、配置 ServerSocketChannel ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.socket().bind(new InetSocketAddress(9998)); serverChannel.configureBlocking(false); // 创建 Selecto

绝圣弃智-零的博客 2137

Netty 对 Java NIO 空轮询问题的处理

文章目录1. Java NIO 在 Linux 平台的空轮询问题处理的思路1. Java NIO 新增状态2. 重建 Selector2. Netty 的处理 1. Java NIO 在 Linux 平台的空轮询问题 处理的思路 1. Java NIO 新增状态 2. 重建 Selector 2. Netty 的处理 ...

谈谈1974 938

高并发网络通信Netty之空轮询问题

Netty空轮询在没有事件被触发时形成了空转,导致 CPU 占用 100%,系统资源白白浪费。

技术改变命运,创新驱动中国 809

Netty——NIO 空轮询 bug

本文讲解了 NIO 的空轮询 bug,并介绍了一种简单的解决方案和 Netty 的解决方案。

qq_61350148的博客 1250

java epoll bug_JDK Epoll空轮询bug

bug表现epoll bug正常情况下,selector.select()操作是阻塞的,只有被监听的fd有读写操作时,才被唤醒但是,在这个bug中,没有任何fd有读写请求,但是select()操作依旧被唤醒很显然,这种情况下,selectedKeys()返回的是个空数组然后按照逻辑执行到while(true)处,循环执行,导致死循环。bug原因JDK bug列表中有两个相关的bug报告:JDK-6...

weixin_42166105的博客 476

select,poll,epoll对比及epoll空轮询

目录 一、select,poll,epoll对比 1.select 2.poll 3.epoll 4.总结 二、epoll空轮询bug及处理 1.Selector BUG出现的原因 2.Netty的解决办法 一、select,poll,epoll对比 1.select 支持一个进程所能打开的最大连接数 单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是3232,同理64位机器上FD_SETSIZE为3264),当然我们可以

半桶水的博客 1060

Netty零拷贝&解决空轮询

Netty零拷贝&解决空轮询

m0_57237237的博客 613

应用服务器中对JDK的epoll空转bug的处理

原文链接:应用服务器中对JDK的epoll空转bug的处理 前面讲到了epoll的一些机制,与select和poll等传统古老的IO多路复用机制的一些区别,这些区别实质可以总结为一句话, 就是epoll将重要的基于事件的fd集合放在了内核中来完成,因为内核是高效的,所以很多关于fd事件监听集合的操作也是高效的, 不方便的就是,因为在内核中,所以我们需要通过系统调用来调用关于fd操作集合,而不...

weixin_30624825的博客 554

【空轮询克星】Netty如何根治JDK NIO的CPU 100% Bug?高性能背后的稳定性设计

想象大厦的电梯控制系统:fill:#333;color:#333;color:#333;fill:none;正常情况:电梯请求控制系统调度电梯空轮询Bug:虚假请求疯狂空转JDK NIO空轮询现象本应在有事件时返回但Bug会导致无事件时立即返回0结果:CPU疯狂空转飙升至100%!真实灾难案例某交易所系统半夜CPU 100%,导致早盘无法开市,损失超$2亿!

全栈开发工程师 1548

NIO中Epoll Bug产生的原因以及Netty中是如何解决的

JDK NIO中Epoll空轮询Bug产生的原因以及Netty中是如何解决的 产生原因 正常情况下,selector.select()操作是阻塞的,只有被监听的fd有读写操作时,才被唤醒。但是,在这个bug中,没有任何fd有读写请求,但是select()操作依旧被唤醒很显然,这种情况下,selectedKeys()返回的是个空数组,然后按照逻辑执行到while(true)处,循环执行,导致死循环。 Netty的解决方法 对Selector的select操作周期进行统计,每完成一次空的select操作进行

qq_40751552的博客 1384

JDK源码分析 NIO实现

调用本地native方法 package sun.nio.ch; public class IOUtil { ... public static native void configureBlocking(FileDescriptor var0, boolean var1) throws IOException; 对应jdk文件位置: https://blog.csdn.ne...

深度Java 2053

Netty源码解析-Netty解决Nio epoll bug

前言: Netty作为一个优秀的NIO框架,不仅仅是对JDK NIO的使用进行封装,使其更易用,而且对其一些原生BUG也进行了修复。其中epoll cpu 100%的一个bug的修复更是经典。 1.bug产生的场景 常规使用NIO ServerSocket来接收连接的方式如下: Selector selector = Selector.open(); while (true) { int count = selector.select(); Set<SelectionKe

恐龙弟旺仔的博客 1889
上一篇: Reactor线程模型深度解析
下一篇: Netty源码分析---PooledByteBufAllocator 源码深度解析
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值