NIO open 函数调用栈

当我们使用 NIO 创建 ServerSocketChannel 的时候,会调用 ServerSocketChannel.open() 方法

  1. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

open 方法会创建一个 SelectorProvider

  1. // open
  2. public static ServerSocketChannel open() throws IOException {
  3. return SelectorProvider.provider().openServerSocketChannel();
  4. }

SelectorProvider.provider() 会调用 JDK 底层的 sun.nio.ch.DefaultSelectorProvider.create() 来创建 provider

  1. public static SelectorProvider provider() {
  2. synchronized (lock) {
  3. if (provider != null)
  4. return provider;
  5. return AccessController.doPrivileged(
  6. new PrivilegedAction<SelectorProvider>() {
  7. public SelectorProvider run() {
  8. if (loadProviderFromProperty())
  9. return provider;
  10. if (loadProviderAsService())
  11. return provider;
  12. provider = sun.nio.ch.DefaultSelectorProvider.create();
  13. return provider;
  14. }
  15. });
  16. }
  17. }

那么问题来了,NIO 底层到底是使用的那种 I/O 多路复用模型呢?

我们继续往下看(笔者是 windows),当我们点击 create() 方法的时候,返回了一个 WindowsSelectorProvider,此时 ServerSocketChannel.open() 方法就全部执行完毕,创建了一个 WindowsSelectorProvider 对象

  1. public static SelectorProvider create() {
  2. return new WindowsSelectorProvider();
  3. }

代码继续向下执行,按照 NIO 创建规矩,我们需要创建一个 Selector 对象

  1. Selector selector = Selector.open();

当我们使用调用 Selector.open() 创建 Selector 对象的时候,会调用

  1. public static Selector open() throws IOException {
  2. return SelectorProvider.provider().openSelector();
  3. }

这个时候我们知道 SelectorProvider.provider() 返回的是 WindowsSelectorProvider 对象,也就是调用了 WindowsSelectorProvider 对象的 openSelector() 来创建 Selector 对象

  1. public class WindowsSelectorProvider extends SelectorProviderImpl {
  2. public WindowsSelectorProvider() {
  3. }
  4. public AbstractSelector openSelector() throws IOException {
  5. return new WindowsSelectorImpl(this);
  6. }
  7. }

此时,我们可以看到 openSelector() 返回了一个 new WindowsSelectorImpl(this) 对象,最终会走到 doSelect() 方法,执行 this.subSelector.poll() 方法

  1. try {
  2. this.subSelector.poll();
  3. } catch (IOException var7) {
  4. this.finishLock.setException(var7);
  5. }

而 this.subSelector.poll() 会调用 poll0() 方法

  1. private int poll() throws IOException {
  2. return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress, Math.min(WindowsSelectorImpl.this.totalChannels, 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
  3. }

poll0() 方法是一个 native 方法

  1. 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 文件,打开后,找到方法定义

  1. Java_sun_nio_ch_WindowsSelectorImpl_00024SubSelector_poll0(JNIEnv *env, jobject this,
  2. jlong pollAddress, jint numfds,
  3. jintArray returnReadFds, jintArray returnWriteFds,
  4. jintArray returnExceptFds, jlong timeout)

继续向下看找到一段逻辑

  1. if ((result = select(0 , &readfds, &writefds, &exceptfds, tv)) == SOCKET_ERROR) {
  2. // 省略...
  3. }

至此,我们可以判断 windows 操作系统下,NIO 使用的模型是 select I/O 多路复用模型
**
那么 linux 操作系统下,也是使用 select I/O 多路复用模型么?

OpenJDK Linux 源码追踪

根据上述经验,我们先看看 linux 操作系统下 sun.nio.ch.DefaultSelectorProvider.create() 创建的是什么?

  1. public static SelectorProvider create() {
  2. String osname = AccessController
  3. .doPrivileged(new GetPropertyAction("os.name"));
  4. if (osname.equals("SunOS"))
  5. return createProvider("sun.nio.ch.DevPollSelectorProvider");
  6. if (osname.equals("Linux"))
  7. return createProvider("sun.nio.ch.EPollSelectorProvider");
  8. return new sun.nio.ch.PollSelectorProvider();
  9. }

我们可以看出,Linux 操作系统下,使用的是 EPollSelectorProvider

至此,我们可以知道,NIO 中的 select I/O 多路复用模型是根据操作系统来定的,如果是 windows 就使用 select 模型,如果是 Linux 就使用 epoll 模型