jdk1.6空轮询Bug的原因及解决方法
简述
本文主要介绍一下jdk1.6版本中的NIO Selector空轮询BUG,描述一下BUG的现象及原因,以及Netty中如何巧妙的规避了这个bug。
为什么要写这篇文章,说来惭愧,很久以前面试官问我,知道jdk空轮询问题吗,为什么会有这个问题,如何解决这个问题?我没答上来。。
Selector空轮询BUG
重现场景步骤
- 服务端等待连接
- 客户端发起连接,发送消息
- 服务端接受连接,并注册监听通道的OP_READ
- 服务端读取消息,从感兴趣事件集合中移除OP_READ
- 客户端关闭连接
- 服务端给客户端发送消息
- 服务端select方法不再阻塞,无限被唤醒并且返回值为0.
实验结果
在window上,此步骤下,是正常的。但是在linux机器上,selector陷入了死循环(cpu100%)。
上面是官方JDK-6670302 : (se) NIO selector wakes up with 0 selected keys infinitely [lnx 2.4]给出的重现实验步骤。
bug根源
官方在6670302-BUG页面上好像并不认为是jdk的bug。也没给出具体原因。而把原因归结为Linux Kernel 2.4版本的bug(JDK-6481709)。官方认为linux 内核2.6版本解决这个bug并且也发行了4年了,更建议大家使用linux kernel2.6。
笔者愚钝,看了JDK-6481709这个BUG后,并没发现产生的原因。
后来终于在JDK-6403933 : (se) Selector doesn't block on Selector.select(timeout) (lnx)这个bug里找到了貌似是答案的答案。
问题产生于linux的epoll(显然是被甩锅了)。如果一个socket文件描述符,注册的事件集合码为0,然后连接突然被对端中断,那么epoll会被POLLHUP或者有可能是POLLERR事件给唤醒,并返回到事件集中去。这意味着,Selector会被唤醒,即使对应的channel兴趣事件集是0,并且返回的events事件集合也是0。
简而言之就是,jdk认为linux的epoll告诉我事件来了,但是jdk没有拿到任何事件(READ、WRITE、CONNECT、ACCPET)。但此时select()方法不再选择阻塞了,而是选择返回了0。
BUG现状
官方页面中显示jdk6u4版本和jdk7b12版本都已解决。实际上在1.6,1.7,1.8都没有解决。
也就是说linux内核为2.4的,使用jdk6u4以下的开发者,仍可能遭遇此bug。
其实官方也提供了解决的思路。
解决方案
JDK-6403933里面提到了几种方案,我总结一下:
- 取消对应的key,马上刷新Selector。就是在重现步骤中的第4步,立马调用selector.selectNow刷新一次selector。
如果注册到selector兴趣事件集为0,则直接取消注册。 如果注册到selector兴趣事件集不为0,则需要将linux epoll事件POLLHUP/POLLERR转化为OP_READ 或者OP_WRITE。由谁决定转化呢,笔者认为应该由jdk。这样程序就有机会探测到IO异常。
- 丢弃旧的selector,重新构造一个。
三种方法,笔者认为1、2都可能没有彻底解决问题。第一种,selectNow的调用,只是select的非阻塞版本,非常有可能在多线程中和selectionKey.cancel同时调用的。第二种方案,即使读写channel数据时抛出了IO异常,不是所有人都会记得关闭此Channel并deregister这个channel。
至于第三种方案,应该是可行的,因为重新构造了selector,需要重新注册channnel到其上,并注册感兴趣事件,重新注册的过程中有机会检测channel的可用性。但是什么时候需要重新创建一个呢?这可能就需要一些检测空轮询的机制了
Netty3中如何解决
netty3采用的是第三种方案,检测重点是select函数是否返回了0。代码在AbstractNioSelector类中
if (timeBlocked < minSelectTimeout) {
boolean notConnected = false;
//循环遍历所有selectionKey,剔除可能导致selector唤醒的被关闭的channel
for (SelectionKey key : selector.keys()) {
SelectableChannel ch = key.channel();
try {
if (ch instanceof DatagramChannel && !ch.isOpen() ||
ch instanceof SocketChannel && !((SocketChannel) ch).isConnected()) {
notConnected = true;
//发现了关闭的通道赶紧取消以防万一,不会再下次select的key集合中
key.cancel();
}
} catch (CancelledKeyException e) {
// ignore
}
}
if (notConnected) {
selectReturnsImmediately = 0;
} else {
//到这里,发生了一次selector在关闭的通道上被唤醒,所以记数+1
//防止引起jdk epoll的bug
selectReturnsImmediately++;
}
} else {
selectReturnsImmediately = 0;
}
if (selectReturnsImmediately == 1024) {
//发生了1024次了,应该碰到著名的epollbug了,
//重新构造一个selector
rebuildSelector();
selector = this.selector;
selectReturnsImmediately = 0;
wakenupFromLoop = false;
continue;
}
这里,netty通过线程不断循环检测select是否返回0,若发生了1024次(次数不重要,若发生了epoll bug,肯定次数飙升),则开始重建selector。
看看重建的seletor代码,rebuildSelector方法:
public void rebuildSelector() {
final Selector oldSelector = selector;
final Selector newSelector;
if (oldSelector == null) {
return;
}
try {
newSelector = SelectorUtil.open();
} catch (Exception e) {
logger.warn("Failed to create a new Selector.", e);
return;
}
// 将老的channel重新注册到新selector上
int nChannels = 0;
for (; ; ) {
try {
for (SelectionKey key : oldSelector.keys()) {
try {
if (key.channel().keyFor(newSelector) != null) {
continue;
}
int interestOps = key.interestOps();
key.cancel();
key.channel().register(newSelector, interestOps, key.attachment());
nChannels++;
} catch (Exception e) {
logger.warn("Failed to re-register a Channel to the new Selector,", e);
close(key);
}
}
} catch (ConcurrentModificationException e) {
continue;
}
break;
}
selector = newSelector;
try {
//关闭老的selector
oldSelector.close();
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to close the old Selector.", t);
}
}
}
- AbstractNioSelector会启动一个线程,在当前selector会循环调用selector.select(timeout)方法,如果在timeout时间之内,selector返回了,则需要检测唤醒它的SelectionKey里面,有没有未关闭的连接channel存在。有则取消这个key。这能防止引起epoll bug。
- 什么时候可以认为发生了epoll bug呢,就是阻塞的select方法提前被唤醒了并且返回了0。有就增加计数器,计数器的值很快会到1024,然后就可以重建一个selector,抛弃那个已经在无限轮回的oldSelector。
- 将oldselector上的key都取消掉,重新注册到新的selector上。关闭oldSelector。
总结
本文讲述了jdk epoll bug的原因,及解决方法。原因是给关闭的通道发消息。解决的最好方法,是重建一个selector。
jdk1.6空轮询Bug的原因及解决方法的更多相关文章
- Java nio 空轮询bug到底是什么
编者注:Java nio 空轮询bug也就是Java nio在Linux系统下的epoll空轮询问题. epoll机制是Linux下一种高效的IO复用方式,相较于select和poll机制来说.其高效 ...
- Nginx 502 Bad Gateway 错误的原因及解决方法
http://my.oschina.net/zhouyuan/blog/118708 刚才在调试程序的时候,居然服务器502错误,昨天晚上也发生了,好像我没有做非常规的操作. 然后网上寻找了下答案, ...
- 稳定性专题 | StackOverFlowError 常见原因及解决方法
导读 『StabilityGuide』是阿里多位阿里技术工程师共同发起的稳定性领域的知识库开源项目,涵盖性能压测.故障演练.JVM.应用容器.服务框架.流量调度.监控.诊断等多个技术领域,以更结构化的 ...
- Invalid bound statement (not found)出现原因和解决方法
Invalid bound statement (not found)出现原因和解决方法 前言: 想必各位小伙伴在码路上经常会碰到奇奇怪怪的事情,比如出现Invalid bound statement ...
- coreseek常见错误原因及解决方法
coreseek常见错误原因及解决方法 Coreseek 中文全文检索引擎 Coreseek 是一款中文全文检索/搜索软件,以GPLv2许可协议开源发布,基于Sphinx研发并独立发布,专攻中文搜索和 ...
- .NET 3.5 安装错误的四个原因及解决方法
.net framework 3.5 安装错误的四个常见原因及解决方法,飓风软件站整理,转载请注明. 1.清除所有版本 .NET Framework 安装错误后在系统中遗留的文件: 如果您以往安装过 ...
- Java ConcurrentModificationException异常原因和解决方法
Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...
- oracle 索引失效原因及解决方法
oracle 索引失效原因及解决方法 2010年11月26日 星期五 17:10 一.以下的方法会引起索引失效 1,<>2,单独的>,<,(有时会用到,有时不会)3,like ...
- Nginx 499错误的原因及解决方法
今天进行系统维护,发现了大量的499错误, 499错误 ngx_string(ngx_http_error_495_page), /* 495, https certificate error */n ...
随机推荐
- 【ORA错误大全】 ORA-19527
在做主备切换的时候,需要将备库的联机日志文件清除(clear online redo logfile),为了加快switchover的速度,Oracle10g在将备库置于manged standby状 ...
- mybatis的update操作的几种动态更新
mybatis是我们开发者常用的dao框架,亿轻巧灵活为特征,在crud操作中,动态更新是常用的操作.我搜集了两种动态更新的sql写法,以备日后备用! 方法1: update loan_product ...
- hibernate增删改
public class HibernateUtils { private static SessionFactory sessionFactory=null; static{ //获取config ...
- (day20)javaEE三大组件之一Servlet (简介(二)servletconfig,servletContext,session,cookie,request,response,out)
javaEE是服务器编程,javaEE提供了服务器的接口让具体的服务器去创建实现的对象 JavaEE是sun公司为了解决企业级开发定义的一套技术,只提供了规范,具体的实现是由服务器完成的 servle ...
- java基础-day29
第06天 MySQL数据库 今日内容介绍 u MySQL单表查询 u SQL约束 u 多表操作 第1章 MySQL单表查询 1.1 SQL单表查询--排序 1.1.1 排序格式 通过order ...
- Spring的两种代理JDK和CGLIB的区别浅谈
一.原理区别: java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. 而cglib动态代理是利用asm开源包,对代理对象类的class文件 ...
- brctl命令
有五台主机.其中一台主机装有linux ,安装了网桥模块,而且有四块物理网卡,分别连接同一网段的其他主机.我们希望其成为一个网桥,为其他四台主机(IP分别为192.168.1.2 ,192.168.1 ...
- bash编程-Shell基础
1. Shell脚本执行方式 直接运行,需要在脚本文件头部指定解释器,如#!/bin/bash ./myshell.sh 运行时指定shell解释器 bash myshell.sh 2. Shell命 ...
- asp.net core sdk & runtime 镜像[已更新至2.2.0]
在官方镜像的脚本上, 增加了System.Drawing相关的依赖库 以北京时间为默认的时间 2.2.0 Windows SDK地址: 官方: https://dotnetcli.blob.core. ...
- 索引视图DEMO1
--use tempdb ----------------------在创建视图和所有底层表时,必须打开ANSI_NULLS以及QUOTED_IDENTIFIER选项 --SET ANSI_NULLS ...