java nio空轮循_Netty笔记-JavaNIO的空轮询Bug及Netty应对

本文详细介绍了Java NIO在Linux环境下由于epoll实现的bug导致的空轮询问题,该问题可能导致CPU使用率100%。Netty通过设置超时时间、监控空轮询次数并重建Selector来解决这个问题。当空轮询次数超过预设阈值时,Netty会创建新Selector并将原有通道注册到新Selector,然后关闭旧Selector,从而避免空轮询死循环。
摘要由CSDN通过智能技术生成

问题描述

若Selector的轮询结果为空,也没有调用wakeup或新消息处理,Selector.select()被唤醒而发生空轮询,CPU使用率100%。

因为java的epoll实现存在bug,而linux下NIO底层使用的是epoll来实现的(而windows不是),因此该Bug只在linux系统下。

//获得一个 Selector 对象

selector = Selector.open();

//创建一个socket对象

serverSocketChannel = ServerSocketChannel.open();

//在该socket上的读写都不阻塞,也就是读写操作立即返回,无论有没有数据。这个设置对于POSIX中的O_NONBLOCK标志。

serverSocketChannel.configureBlocking(false);

//在同一个主机上关闭了服务器程序,紧接着再启动该服务器程序时可以顺利绑定相同的端口

serverSocketChannel.socket().setReuseAddress(true);

//绑定地址

serverSocketChannel.socket().bind(new InetSocketAddress(port));

log.info("服务器启动,等待连接...");

for(;;){

int num = selector.select();

if (num==0){

log.error("select wakes up with zero!!!");

}

//或者 while(true){ selector.select();

Set readyKeys = selector.selectedKeys();

Iterator it = readyKeys.iterator();

while (it.hasNext()) {

SelectionKey key = null;

try {

key = it.next();

if (key.isReadable()){

receive(key);

}

//...

}

}

}

int num = selector.select();一般情况下是阻塞模式,但确被唤醒,num==0即selectionKey为空,则 while (it.hasNext()) { ..}不会执行。

Netty中解决该bug的方法

对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数,若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug。

重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。

详解代码

1、selector.select(timeout)

设置了一个超时时间,selector有以下4种情况跳出阻塞

1.有事件发生

2.wakeup

3.超时

4.空轮询bug

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

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

private void select(boolean oldWakenUp) throws IOException {//节选

for(;;)

int selectedKeys = selector.select(timeoutMillis);

selectCnt ++;

if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {

// - Selected something,

// - waken up by user, or

// - the task queue has a pending task.

// - a scheduled task is ready for processing

break;

}

long time = System.nanoTime();

if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {

// timeoutMillis elapsed without anything selected.

// 超时

selectCnt = 1;

} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&

selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {//默认值512

// The code exists in an extra method to ensure the method is not too big to inline as this

// branch is not very likely to get hit very frequently.

// 空轮询一次 cnt+1 如果一个周期内次数超过512,则假定发生了空轮询bug,重建selector

selector = selectRebuildSelector(selectCnt);

selectCnt = 1;

break;

}

}

}

/**

* Replaces the current {@link Selector} of this event loop with newly created {@link Selector}s to work

* around the infamous epoll 100% CPU bug.

* 新建一个selector来解决空轮询bug

*/

public void rebuildSelector() {

if (!inEventLoop()) {

execute(new Runnable() {

@Override

public void run() {

rebuildSelector0();

}

});

return;

}

rebuildSelector0();

}

private void rebuildSelector0() {

final Selector oldSelector = selector;

final SelectorTuple newSelectorTuple;

//新建一个selector

newSelectorTuple = openSelector();

// 将旧的selector的channel全部拿出来注册到新的selector上

int nChannels = 0;

for (SelectionKey key: oldSelector.keys()) {

Object a = key.attachment();

if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {

continue;

}

int interestOps = key.interestOps();

key.cancel();

SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);

if (a instanceof AbstractNioChannel) {

// Update SelectionKey

((AbstractNioChannel) a).selectionKey = newKey;

}

nChannels ++;

}

selector = newSelectorTuple.selector;

unwrappedSelector = newSelectorTuple.unwrappedSelector;

// time to close the old selector as everything else is registered to the new one

//关掉旧的selector

oldSelector.close();

}

参考:

李林峰的《Netty权威指南》(有关此bug的说明)

java NIO的空轮询bug 以及Netty的解决办法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值