NIO open 函数调用栈
当我们使用 NIO 创建 ServerSocketChannel 的时候,会调用 ServerSocketChannel.open() 方法
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
open 方法会创建一个 SelectorProvider
// openpublic 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 模型
