NIO open 函数调用栈
当我们使用 NIO 创建 ServerSocketChannel 的时候,会调用 ServerSocketChannel.open() 方法
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
open 方法会创建一个 SelectorProvider
// open
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
SelectorProvider.provider() 会调用 JDK 底层的 sun.nio.ch.DefaultSelectorProvider.create() 来创建 provider
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
那么问题来了,NIO 底层到底是使用的那种 I/O 多路复用模型呢?
我们继续往下看(笔者是 windows),当我们点击 create() 方法的时候,返回了一个 WindowsSelectorProvider,此时 ServerSocketChannel.open() 方法就全部执行完毕,创建了一个 WindowsSelectorProvider 对象
public static SelectorProvider create() {
return new WindowsSelectorProvider();
}
代码继续向下执行,按照 NIO 创建规矩,我们需要创建一个 Selector 对象
Selector selector = Selector.open();
当我们使用调用 Selector.open() 创建 Selector 对象的时候,会调用
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
这个时候我们知道 SelectorProvider.provider() 返回的是 WindowsSelectorProvider 对象,也就是调用了 WindowsSelectorProvider 对象的 openSelector() 来创建 Selector 对象
public class WindowsSelectorProvider extends SelectorProviderImpl {
public WindowsSelectorProvider() {
}
public AbstractSelector openSelector() throws IOException {
return new WindowsSelectorImpl(this);
}
}
此时,我们可以看到 openSelector() 返回了一个 new WindowsSelectorImpl(this) 对象,最终会走到 doSelect() 方法,执行 this.subSelector.poll() 方法
try {
this.subSelector.poll();
} catch (IOException var7) {
this.finishLock.setException(var7);
}
而 this.subSelector.poll() 会调用 poll0() 方法
private int poll() throws IOException {
return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress, Math.min(WindowsSelectorImpl.this.totalChannels, 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
}
poll0() 方法是一个 native 方法
private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);
此时我们就需要到 JDK 源码中查看 poll0 的方法定义了
OpenJDK Windows 源码追踪
我们打卡 \OpenJDK\jdk8u\jdk\src\windows\native\sun\nio\ch 目录下,找到了 WindowsSelectorImpl.c 文件,打开后,找到方法定义
Java_sun_nio_ch_WindowsSelectorImpl_00024SubSelector_poll0(JNIEnv *env, jobject this,
jlong pollAddress, jint numfds,
jintArray returnReadFds, jintArray returnWriteFds,
jintArray returnExceptFds, jlong timeout)
继续向下看找到一段逻辑
if ((result = select(0 , &readfds, &writefds, &exceptfds, tv)) == SOCKET_ERROR) {
// 省略...
}
至此,我们可以判断 windows 操作系统下,NIO 使用的模型是 select I/O 多路复用模型
**
那么 linux 操作系统下,也是使用 select I/O 多路复用模型么?
OpenJDK Linux 源码追踪
根据上述经验,我们先看看 linux 操作系统下 sun.nio.ch.DefaultSelectorProvider.create() 创建的是什么?
public static SelectorProvider create() {
String osname = AccessController
.doPrivileged(new GetPropertyAction("os.name"));
if (osname.equals("SunOS"))
return createProvider("sun.nio.ch.DevPollSelectorProvider");
if (osname.equals("Linux"))
return createProvider("sun.nio.ch.EPollSelectorProvider");
return new sun.nio.ch.PollSelectorProvider();
}
我们可以看出,Linux 操作系统下,使用的是 EPollSelectorProvider
至此,我们可以知道,NIO 中的 select I/O 多路复用模型是根据操作系统来定的,如果是 windows 就使用 select 模型,如果是 Linux 就使用 epoll 模型