https://blog.csdn.net/speakguoer/article/details/120132750
https://blog.csdn.net/qq_40751552/article/details/111715056
文章目录
产生原因
正常情况下,select()方法在没有事件时,程序一直阻塞在该方法,但是在没有任何事件的情况下,该方法被唤醒,但是在程序对seclectionKey进行遍历时,却没有任何key,导致程序一直在循环中不能执行下去,从而导致CPU100%。 具体解释为Linux kernel 2.6中的一部分,poll和epoll会对已经连接好后却突然中断连接的socket给eventSet返回一个集合,集合为POLLHUP,也可能为POLLERR,因为eventSet的事件集合发生了变化,导致Selector被唤醒。
Java官方解释
Bug ID: JDK-6670302 (se) NIO selector wakes up with 0 selected keys infinitely [lnx 2.4]
问题复现
环境:Centos7 (其实只要是Linux系统就可以) JDK版本:JDK6u3 (一定要是update3,后边版本对该问题修复,不太容易复现) 下载链接:Java Archive Downloads - Java SE 6 客户端代码
import java.io.*;
import java.net.*;
public class TestClient {
private static final long SLEEP_PERIOD = 5000L; // 5 seconds
private String host;
private int port;
public TestClient(String host, int port) {
this.host = host;
this.port = port;
}
public static void main(String[] args) throws Throwable {
if (args.length < 2 || args[0].equals("127.0.0.1") || args[0].equals("localhost")) {
System.err.println("Usage : java TestClient <host name> <port> (host name should not be localhost)");
System.exit(0);
}
new TestClient(args[0], Integer.parseInt(args[1])).start();
}
public void start() throws Throwable {
Socket socket = new Socket(host, port);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream()),
true /* auto flush */);
out.println("abcdef");
System.out.println("1. CLIENT CONNECTED AND WROTE MESSAGE");
Thread.sleep(SLEEP_PERIOD);
// socket.shutdownOutput();
socket.close();
System.out.println("4. CLIENT SHUTDOWN OUTPUT");
Thread.sleep(SLEEP_PERIOD * 3);
}
}
服务端代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class TestServer{
private static final long SLEEP_PERIOD = 5000L; // 5 seconds
private static final int BUFFER_SIZE = 8192;
private int port;
public TestServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Throwable {
if (args.length < 1) {
System.err.println("Usage : java TestServer <port>");
System.exit(0);
}
new TestServer(Integer.parseInt(args[0])).start();
}
public void start() throws Throwable {
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket server = serverChannel.socket();
server.bind(new InetSocketAddress(port));
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
SocketChannel clientChannel = null;
System.out.println("0. SERVER STARTED TO LISTEN");
boolean writeNow = false;
while (true) {
try {
// wait for selection
int numKeys = selector.select();
if (numKeys == 0) {
System.err.println("select wakes up with zero!!!");
}
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey selected = (SelectionKey) it.next();
int ops = selected.interestOps();
try {
// process new connection
if ((ops & SelectionKey.OP_ACCEPT) != 0) {
clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
// register channel to selector
clientChannel.register(selector, SelectionKey.OP_READ, null);
System.out.println("2. SERVER ACCEPTED AND REGISTER READ OP : client - " + clientChannel.socket().getInetAddress());
}
if ((ops & SelectionKey.OP_READ) != 0) {
// read client message
System.out.println("3. SERVER READ DATA FROM client - " + clientChannel.socket().getInetAddress());
readClient((SocketChannel) selected.channel(), buffer);
// deregister OP_READ
System.out.println("PREV SET : " + selected.interestOps());
selected.interestOps(selected.interestOps() & ~SelectionKey.OP_READ);
System.out.println("NEW SET : " + selected.interestOps());
Thread.sleep(SLEEP_PERIOD * 2);
new WriterThread(clientChannel).start();
}
} finally {
// remove from selected key set
it.remove();
}
}
} catch (IOException e) {
System.err.println("IO Error : " + e.getMessage());
}
}
}
public void readClient(SocketChannel channel, ByteBuffer buffer) throws IOException {
try {
buffer.clear();
int nRead = channel.read(buffer);
if (nRead < 0) {
channel.close();
return;
}
if (buffer.position() != 0) {
int size = buffer.position();
buffer.flip();
byte[] bytes = new byte[size];
buffer.get(bytes);
System.out.println("RECVED : " + new String(bytes));
}
} catch (IOException e) {
System.err.println("IO Error : " + e.getMessage());
channel.close();
}
}
static class WriterThread extends Thread {
private SocketChannel clientChannel;
public WriterThread(SocketChannel clientChannel) {
this.clientChannel = clientChannel;
}
public void run() {
try {
writeClient(clientChannel);
System.out.println("5. SERVER WRITE DATA TO client - " + clientChannel.socket().getInetAddress());
} catch (IOException e) {
System.err.println("5. SERVER WRITE DATA FAILED : " + e);
}
}
public void writeClient(SocketChannel channel) throws IOException {
try {
ByteBuffer buffer = ByteBuffer.wrap("zwxydfdssdfsd".getBytes());
int total = buffer.limit();
int totalWrote = 0;
int nWrote = 0;
while ((nWrote = channel.write(buffer)) >= 0) {
totalWrote += nWrote;
if (totalWrote == total) {
break;
}
}
} catch (IOException e) {
System.err.println("IO Error : " + e.getMessage());
channel.close();
}
}
}
}
操作步骤: 1、将上述代码使用前边下载好的jdk6u3打成jar包,可以用IDE工具也可以用命令行 命令行输入:jar cvf xxx.jar xxx
,其中xxx表示项目目录名称 2、打好jar包后,在Linux系统中执行jar包 终端一启动服务端:java -classpath 包名.TestServer 77777
终端二启动客户端:java -classpath 包名.TestClient XXXXX 77777
,其中XXXXX表示当前机器的IP 等待客户端退出,在客户端屏幕显示内容
在服务器终端显示以上内容,表示bug复现成功。
产生原因
正常情况下,selector.select()操作是阻塞的,只有被监听的fd有读写操作时,才被唤醒。但是,在这个bug中,没有任何fd有读写请求,但是select()操作依旧被唤醒很显然,这种情况下,selectedKeys()返回的是个空数组,然后按照逻辑执行到while(true)处,循环执行,导致死循环。
Netty的解决方法
对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数。
若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug。
重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。