学习笔记----从JVM的角度认识Java的NIO

577 阅读4分钟

从JVM的角度认识Java的NIO

1. 定义

java.nio (NIO stands for non-blocking I/O[1]) is a collection of Java programming language APIs that offer features for intensive I/O operations. It was introduced with the J2SE 1.4 release of Java by Sun Microsystems to complement an existing standard I/O. NIO was developed under the Java Community Process as JSR 51.[2] An extension to NIO that offers a new file system API, called NIO.2, was released with Java SE 7 ("Dolphin").[3]

NIO 有三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)

1、channel 类似于流,每个 channel 对应一个 buffer缓冲区,buffer 底层就是个数组

2、channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理

3、NIO 的 Buffer 和 channel 都是既可以读也可以写

2.例子

public class NIOServer {

    //public static ExecutorService pool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws IOException {
        // 创建一个在本地端口进行监听的服务Socket通道.并设置为非阻塞方式
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //必须配置为非阻塞才能往selector上注册,否则会报错,selector模式本身就是非阻塞模式
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(9000));
        // 创建一个选择器selector
        Selector selector = Selector.open();
        // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            System.out.println("等待事件发生。。");
            // 轮询监听channel里的key,select是阻塞的,accept()也是阻塞的
            int select = selector.select();

            System.out.println("有事件发生了。。");
            // 有客户端请求,被轮询监听到
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //删除本次已处理的key,防止下次select重复处理
                it.remove();
                handle(key);
            }
        }
    }

    private static void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            System.out.println("有客户端连接事件发生了。。");
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //NIO非阻塞体现:此处accept方法是阻塞的,但是这里因为是发生了连接事件,所以这个方法会马上执行完,不会阻塞
            //处理完连接请求不会继续等待客户端的数据发送
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            //通过Selector监听Channel时对读事件感兴趣
            sc.register(key.selector(), SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            System.out.println("有客户端数据可读事件发生了。。");
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //NIO非阻塞体现:首先read方法不会阻塞,其次这种事件响应模型,当调用到read方法时肯定是发生了客户端发送数据的事件
            int len = sc.read(buffer);
            if (len != -1) {
                System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
            sc.write(bufferToWrite);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        } else if (key.isWritable()) {
            SocketChannel sc = (SocketChannel) key.channel();
            System.out.println("write事件");
            // NIO事件触发是水平触发
            // 使用Java的NIO编程的时候,在没有数据可以往外写的时候要取消写事件,
            // 在有数据往外写的时候再注册写事件
            key.interestOps(SelectionKey.OP_READ);
            //sc.close();
        }
    }
}

3.源码解析,深入到JVM层面

  1. ServerSocketChannel ssc = ServerSocketChannel.open();
  • java.nio.channels.ServerSocketChannel#open
public static ServerSocketChannel open() throws IOException {
    //看下provider()的方法,最终返回的是EPollSelectorProvider对象.
    return SelectorProvider.provider().openServerSocketChannel();
}

java.nio.channels.spi.SelectorProvider#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;
                        //注意看这里,DefaultSelectorProvider
                        provider = sun.nio.ch.DefaultSelectorProvider.create();
                        return provider;
                    }
                });
    }
}

我都知道java语言是跨平台的,所以这个DefaultSelectorProvider中的create()方法,在Windows平台上返回的是sun.nio.ch.WindowsSelectorProvider,在Linux平台上返回的是sun.nio.ch.EPollSelectorProvider,我们主要研究Linux平台的,因为服务一般都是这个平台的。

sun.nio.ch.DefaultSelectorProvider

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();
}

接着我们要看openServerSocketChannel()方法,其实是sun.nio.ch.SelectorProviderImpl,它是sun.nio.ch.EPollSelectorProvider的父类

sun.nio.ch.SelectorProviderImpl#openServerSocketChannel

public ServerSocketChannel openServerSocketChannel() throws IOException {
    return new ServerSocketChannelImpl(this);
}

sun.nio.ch.ServerSocketChannelImpl#ServerSocketChannelImpl(java.nio.channels.spi.SelectorProvider)

ServerSocketChannelImpl(SelectorProvider var1) throws IOException {
    super(var1);
    //注意看这里,会返回一个Socket对象的文件描述符
    this.fd = Net.serverSocket(true);
    this.fdVal = IOUtil.fdVal(this.fd);
    this.state = 0;
}

sun.nio.ch.Net#serverSocket

static FileDescriptor serverSocket(boolean var0) {
    //socket0是个本地方法,所以我们看Hotspot的源码
    return IOUtil.newFD(socket0(isIPv6Available(), var0, true, fastLoopback));
}
JNIEXPORT int JNICALL
Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6,
                            jboolean stream, jboolean reuse)
{
    int fd;
    int type = (stream ? SOCK_STREAM : SOCK_DGRAM);
#ifdef AF_INET6
    int domain = (ipv6_available() && preferIPv6) ? AF_INET6 : AF_INET;
#else
    int domain = AF_INET;
#endif
    //注意这行代码,这就调用到了Linux内核的代码了,int socket(int domain, int type ,int protocol)这个函数
    //domain参数指定了socket的通信domain。type参数指定了socket类型,这个参数通常在创建流socket时候被指定为SOCK_STREAM
    //protocol总是会被指定为0. -----《Linux-UNIX系统编程手册(上、下册)中文版》第56.2章节
    fd = socket(domain, type, 0);
    if (fd < 0) {
        return handleSocketError(env, errno);
    }

所以我们知道,Java层面的ServerSocketChannel在操作系统底层,就是个Socket对象的文件描述符

  1. Selector selector = Selector.open();

    这个selector变量是个EPollSelectorImpl对象,它里面包含了epoll实例,所以selector 对象可以理解为操作系统的epoll对象

java.nio.channels.Selector#open

public static Selector open() throws IOException {
    //provider()这个方法在上面已经讲述过了,最终返回的是EPollSelectorProvider对象.
    return SelectorProvider.provider().openSelector();
}

我们来看看sun.nio.ch.EPollSelectorProvider#openSelector()方法

public AbstractSelector openSelector() throws IOException {
    //返回了一个EPollSelectorImpl对象
    return new EPollSelectorImpl(this);
}

sun.nio.ch.EPollSelectorImpl(SelectorProvider sp)

EPollSelectorImpl(SelectorProvider sp) throws IOException {
    super(sp);
    long pipeFds = IOUtil.makePipe(false);
    fd0 = (int) (pipeFds >>> 32);
    fd1 = (int) pipeFds;
    //查看这个方法
    pollWrapper = new EPollArrayWrapper();
    pollWrapper.initInterrupt(fd0, fd1);
    fdToKey = new HashMap<>();
}

sun.nio.ch.EPollArrayWrapper()

EPollArrayWrapper() throws IOException {
    // creates the epoll file descriptor
    //查看这个方法,这个是个native方法啊,所以看Hotspot的源码Java_sun_nio_ch_EPollArrayWrapper_epollCreate(JNIEnv *env, jobject this)
    epfd = epollCreate();
    // the epoll_event array passed to epoll_wait
    int allocationSize = NUM_EPOLLEVENTS * SIZE_EPOLLEVENT;
    pollArray = new AllocatedNativeObject(allocationSize, true);
    pollArrayAddress = pollArray.address();

    // eventHigh needed when using file descriptors > 64k
    if (OPEN_MAX > MAX_UPDATE_ARRAY_SIZE)
        eventsHigh = new HashMap<>();
}
JNIEXPORT jint JNICALL
Java_sun_nio_ch_EPollArrayWrapper_epollCreate(JNIEnv *env, jobject this)
{
    /*
     * epoll_create expects a size as a hint to the kernel about how to
     * dimension internal structures. We can't predict the size in advance.
     */
    //系统调用 epoll_create()创建了一个新的 epoll 实例,参数 size 指定了我们想要通过 epoll 实例来检查的文件描述符个数
    //epfd作为函数返回值,epoll_create()返回了代表新创建的 epoll 实例的文件描述符
    //-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.1章节
    int epfd = epoll_create(256);
    if (epfd < 0) {
       JNU_ThrowIOExceptionWithLastError(env, "epoll_create failed");
    }
    return epfd;
}
  1. ssc.register(selector, SelectionKey.OP_ACCEPT);这个selector对象就是上面的EPollSelectorImpl

    在解析源码之前,我们来想想,上面两步,操作系统底层的Socket对象有了,epoll对象也有了,那么是不是要把这个Socket对象绑定到epoll上了呢?

实际调用的是java.nio.channels.SelectableChannel#register(java.nio.channels.Selector, int)

会调用到java.nio.channels.spi.AbstractSelectableChannel#register

public final SelectionKey register(Selector sel, int ops,
                                   Object att)
    throws ClosedChannelException
{
    synchronized (regLock) {
        if (!isOpen())
            throw new ClosedChannelException();
        if ((ops & ~validOps()) != 0)
            throw new IllegalArgumentException();
        if (blocking)
            throw new IllegalBlockingModeException();
        SelectionKey k = findKey(sel);
        if (k != null) {
            k.interestOps(ops);
            k.attach(att);
        }
        if (k == null) {
            // New registration
            synchronized (keyLock) {
                if (!isOpen())
                    throw new ClosedChannelException();
                //看这个方法,sel对象就是上面的EPollSelectorImpl,this就是ServerSocketChannel对象
                k = ((AbstractSelector)sel).register(this, ops, att);
                addKey(k);
            }
        }
        return k;
    }
}

会调用到sun.nio.ch.SelectorImpl#register(AbstractSelectableChannel var1, int var2, Object var3)方法

protected final SelectionKey register(AbstractSelectableChannel ch, int ops, Object attachment) {
        //var1就是ServerSocketChannel对象
        if (!(ch instanceof SelChImpl)) {
            throw new IllegalSelectorException();
        } else {
            //这个对象里面方法了,ServerSocketChannel对象和EPollSelectorImpl对象
            SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
            k.attach(attachment);
            synchronized(this.publicKeys) {
                //3.1 调用EPollSelectorImpl对象的implRegister方法,这里只是java层面的注册
                this.implRegister(k);
            }
            //3.2 这里会把Socket对象的文件描述符放到EPollSelectorImpl对象的pollWrapper的updateDescriptors里面
            //还会把这个Socket对象感兴趣的事件放到eventsLow这个集合中
            k.interestOps(ops);
            return k;
        }
    }

sun.nio.ch.implRegister(SelectionKeyImpl ski)

protected void implRegister(SelectionKeyImpl ski) {
    if (closed)
        throw new ClosedSelectorException();
    //拿到ServerSocketChannel对象
    SelChImpl ch = ski.channel;
    //拿到ServerSocketChannel对象里面的Socket对象文件描述符
    int fd = Integer.valueOf(ch.getFDVal());
    //讲Socket文件描述符和这个Socket对应的Channel对象绑定在fdToKey里面
    fdToKey.put(fd, ski);
    //将Socket对象文件描述符添加的到EPollSelectorImpl对象的集合里面,这是放在java层面的集合里面
    pollWrapper.add(fd);
    keys.add(ski);
}

sun.nio.ch.SelectionKeyImpl#interestOps(int)

public SelectionKey interestOps(int ops) {
        this.ensureValid();
        //看这行代码
        return this.nioInterestOps(ops);
}

//最后会调用到sun.nio.ch.EPollArrayWrapper#setUpdateEvents
private void setUpdateEvents(int fd, byte events, boolean force) {
        if (fd < MAX_UPDATE_ARRAY_SIZE) {
            if (this.eventsLow[fd] != -1 || force) {
                //把这个Socket对象感兴趣的事件放到eventsLow这个集合中
                this.eventsLow[fd] = events;
            }
        } else {
            Integer key = fd;
            if (!this.isEventsHighKilled(key) || force) {
                this.eventsHigh.put(key, events);
            }
        }
}
  1. int select = selector.select();这个selector对象就是上面的EPollSelectorImpl

sun.nio.ch.SelectorImpl#select()

public int select() throws IOException {
    return this.select(0L);
}

调用到sun.nio.ch.SelectorImpl#lockAndDoSelect

private int lockAndDoSelect(long var1) throws IOException {
    synchronized(this) {
        if (!this.isOpen()) {
            throw new ClosedSelectorException();
        } else {
            int var10000;
            synchronized(this.publicKeys) {
                synchronized(this.publicSelectedKeys) {
                    //看这个方法,实际调用的是sun.nio.ch.EPollSelectorImpl.doSelect(long timeout)
                    var10000 = this.doSelect(var1);
                }
            }

            return var10000;
        }
    }
}

sun.nio.ch.EPollSelectorImpl.doSelect(long timeout)

protected int doSelect(long timeout) throws IOException {
    if (closed)
        throw new ClosedSelectorException();
    processDeregisterQueue();
    try {
        //这个方法会在当前线程上注册一个钩子,保证线程在执行的时候如果被中断则进行唤醒
        begin();
        //主要看这个方法,里面做了将Socket文件描述符注册到epoll对象上,以及阻塞等待事件的发生
        pollWrapper.poll(timeout);
    } finally {
        //这个方法会删除begin()方法注册的钩子
        end();
    }
    processDeregisterQueue();
    //这个方法会将pollArray数组中的已经完成的事件取出,找到对应的Key将其
    //更新前面提到的SelectionKey的Set集合,即selector.selectedKeys()方法返回的集合
    int numKeysUpdated = updateSelectedKeys();
    if (pollWrapper.interrupted()) {
        // Clear the wakeup pipe
        pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
        synchronized (interruptLock) {
            pollWrapper.clearInterrupted();
            IOUtil.drain(fd0);
            interruptTriggered = false;
        }
    }
    return numKeysUpdated;
}

sun.nio.ch.EPollArrayWrapper.poll(long timeout)

int poll(long timeout) throws IOException {
    //查看这个方法,在这里把Socket文件描述符注册到epoll对象上
    updateRegistrations();
    //在这里阻塞,等待注册到epoll对象上的Socket文件描述符上的事件发生,调用的是native方法,查看HotSpot方法Java_sun_nio_ch_EPollArrayWrapper_epollWait
    //返回值,updated就是就绪的文件描述符的个数
    //native方法epollWait()
	//pollArrayAddress是发生事件的数组地址,epoll会把发生的事件复制到数组中
	//updated设置为要处理的事件数
    updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);
    for (int i=0; i<updated; i++) {
        //这里判断是否是中断pipe的fd,如果是则直接break跳出循环
        if (getDescriptor(i) == incomingInterruptFD) {
            interruptedIndex = i;
            interrupted = true;
            break;
        }
    }
    //返回就绪的文件描述符的个数
    return updated;
}

sun.nio.ch.updateRegistrations()

private void updateRegistrations() {
    synchronized (updateLock) {
        int j = 0;
        while (j < updateCount) {
            //看到这个updateDescriptors变量,就是在sun.nio.ch.SelectorImpl#register(AbstractSelectableChannel var1, int var2, Object var3)方法里面设置进去的。
            //从更新文件描述符数组中获取文件描述符(刚刚我们已经将fd添加进去)
            int fd = updateDescriptors[j];
            //看这个代码,获取这个Socket感兴趣的事件,就是在sun.nio.ch.SelectionKeyImpl#interestOps(int)这个代码放入的
            short events = getUpdateEvents(fd);
            //registered是一个bitMap跟踪是否文件描述符被epoll注册
            boolean isRegistered = registered.get(fd);
            //下面开始根据,我们在java层面设置的对应Channel其实也就是操作系统底层的Socket对象所感兴趣的事件,来计算出要传给操作系统系统调用的真正的opcode
            int opcode = 0;

            if (events != KILLED) {
                //如果已经注册过且不为0则为修改,如果为0则为删除
                //如果是添加,则标记为注册过,因为在linux层面,
                //对于epoll_ctl()方法,EPOLL_CTL_ADD参数
                //将描述符 fd 添加到 epoll 实例 epfd 中的兴趣列表中去。对于 fd 上我们感兴趣的事件,都
                //指定在 ev 所指向的结构体中,下面会详细介绍。如果我们试图向兴趣列表中添加一个已存在
                //的文件描述符,epoll_ctl()将出现 EEXIST 错误。-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.2章节
                if (isRegistered) {
                    opcode = (events != 0) ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
                } else {
                    //如果没注册过且不为0则为添加
                    opcode = (events != 0) ? EPOLL_CTL_ADD : 0;
                }
                if (opcode != 0) {
                    //查看这个方法,是个navive方法,所以看HotSpot源码Java_sun_nio_ch_EPollArrayWrapper_epollCtl
                    epollCtl(epfd, opcode, fd, events);
                    //更新registered
                    if (opcode == EPOLL_CTL_ADD) {
                        //如果是添加,则标记为注册过,因为在linux层面,
                        //对于epoll_ctl()方法,EPOLL_CTL_ADD参数
                        //将描述符 fd 添加到 epoll 实例 epfd 中的兴趣列表中去。对于 fd 上我们感兴趣的事件,都
                        //指定在 ev 所指向的结构体中,下面会详细介绍。如果我们试图向兴趣列表中添加一个已存在
                        //的文件描述符,epoll_ctl()将出现 EEXIST 错误。-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.2章节
                        registered.set(fd);
                    } else if (opcode == EPOLL_CTL_DEL) {
                        registered.clear(fd);
                    }
                }
            }
            j++;
        }
        updateCount = 0;
    }
}
JNIEXPORT void JNICALL
Java_sun_nio_ch_EPollArrayWrapper_epollCtl(JNIEnv *env, jobject this, jint epfd,
                                           jint opcode, jint fd, jint events)
{
    struct epoll_event event;
    int res;

    event.events = events;
    event.data.fd = fd;
    //系统调用 epoll_ctl()能够修改由文件描述符 epfd 所代表的 epoll 实例中的兴趣列表。
    //参数 fd 指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、
//FIFO、套接字、POSIX 消息队列、inotify 实例、终端、设备,甚至是另一个 epoll 实例的文件
//描述符(例如,我们可以为受检查的描述符建立起一种层次关系)。但是,这里 fd 不能作为普
//通文件或目录的文件描述符(会出现 EPERM 错误)。-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.2章节
    RESTARTABLE(epoll_ctl(epfd, (int)opcode, (int)fd, &event), res);

    /*
     * A channel may be registered with several Selectors. When each Selector
     * is polled a EPOLL_CTL_DEL op will be inserted into its pending update
     * list to remove the file descriptor from epoll. The "last" Selector will
     * close the file descriptor which automatically unregisters it from each
     * epoll descriptor. To avoid costly synchronization between Selectors we
     * allow pending updates to be processed, ignoring errors. The errors are
     * harmless as the last update for the file descriptor is guaranteed to
     * be EPOLL_CTL_DEL.
     */
    if (res < 0 && errno != EBADF && errno != ENOENT && errno != EPERM) {
        JNU_ThrowIOExceptionWithLastError(env, "epoll_ctl failed");
    }
}

事件注册完了后,再回到sun.nio.ch.EPollArrayWrapper.poll(long timeout)方法

接着调用Java_sun_nio_ch_EPollArrayWrapper_epollWait

JNIEXPORT jint JNICALL
Java_sun_nio_ch_EPollArrayWrapper_epollWait(JNIEnv *env, jobject this,
                                            jlong address, jint numfds,
                                            jlong timeout, jint epfd)
{
    struct epoll_event *events = jlong_to_ptr(address);
    int res;

    if (timeout <= 0) {           /* Indefinite or no wait */
        //系统调用 epoll_wait()返回 epoll 实例中处于就绪态的文件描述符信息。单个 epoll_wait()调
        //用能返回多个就绪态文件描述符的信息。-----《Linux-UNIX系统编程手册(上、下册)中文版》第63.4.3章节
        RESTARTABLE(epoll_wait(epfd, events, numfds, timeout), res);
    } else {                      /* Bounded wait; bounded restarts */
        res = iepoll(epfd, events, numfds, timeout);
    }

    if (res < 0) {
        JNU_ThrowIOExceptionWithLastError(env, "epoll_wait failed");
    }
    return res;
}

回到sun.nio.ch.EPollSelectorImpl.doSelect(long timeout)方法,接着查看updateSelectedKeys()这一步。

private int updateSelectedKeys() { int entries = this.pollWrapper.updated; int numKeysUpdated = 0;

private int updateSelectedKeys() {
        //获取已经就绪的事件数,updated就是在`sun.nio.ch.EPollArrayWrapper.poll(long timeout)`这个方法中设置的。
        int entries = this.pollWrapper.updated;
        int numKeysUpdated = 0;

        for(int i = 0; i < entries; ++i) {
            //这个方法是从epollArray中获取就绪的事件关联的fd句柄,就是根据在 epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd)这句代码中最终系统调用epoll_wait(epfd, events, numfds, timeout),在events里设置地址
            //然后在这里根据上面设置的这个地址,和偏移量拿到已经就绪的Socket的文件描述符。
            //上面是个人猜测,然后找挖坑的张师傅求证,结论正确,具体的以后要研究系统调用的epoll_wait源码。暂时可参考,https://mp.weixin.qq.com/s/OmRdUgO1guMX76EdZn11UQ
            int nextFD = this.pollWrapper.getDescriptor(i);
            //根据文件描述符,从fdToKey中获取该文件描述符所对应的SelectionKeyImpl对象,是在`sun.nio.ch.implRegister(SelectionKeyImpl ski)`方法里面绑定的
            SelectionKeyImpl ski = (SelectionKeyImpl)this.fdToKey.get(nextFD);
            if (ski != null) {
                //这个方法是从epollArray中获取就绪的事件类型
                int rOps = this.pollWrapper.getEventOps(i);
                //翻译事件并将其加入selectedkeys集合中
                if (this.selectedKeys.contains(ski)) {
                    if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
                        ++numKeysUpdated;
                    }
                } else {
                    ski.channel.translateAndSetReadyOps(rOps, ski);
                    if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
                        //放入到selectedKeys中,注意看下这个变量
                        this.selectedKeys.add(ski);
                        ++numKeysUpdated;
                    }
                }
            }
        }

        return numKeysUpdated;
    }                    
                    
                    
                    

sun.nio.ch.SelectorImpl.selectedKeys这个变量

在这个构造方法方法sun.nio.ch.SelectorImpl(SelectorProvider sp)

protected SelectorImpl(SelectorProvider sp) {
        super(sp);
        if (Util.atBugLevel("1.4")) {
            this.publicKeys = this.keys;
            this.publicSelectedKeys = this.selectedKeys;
        } else {
            this.publicKeys = Collections.unmodifiableSet(this.keys);
            //会把selectedKeys值作为publicSelectedKeys的成员变量,所以它俩相当于同一个集合
            this.publicSelectedKeys = Util.ungrowableSet(this.selectedKeys);
        }
}

所以在例子中的Iterator<SelectionKey> it = selector.selectedKeys().iterator();

sun.nio.ch.SelectorImpl.selectedKeys()

public Set<SelectionKey> selectedKeys() {
        if (!this.isOpen() && !Util.atBugLevel("1.4")) {
            throw new ClosedSelectorException();
        } else {
            //所以这里返回的publicSelectedKeys实际就是上面的selectedKeys,里面放了就绪的Socket事件的文件描述符。
            return this.publicSelectedKeys;
        }
    }

再来看看例子中SocketChannel sc = ssc.accept();这行代码

它最终会调用到sun.nio.ch.ServerSocketChannelImpl#accept0然后进行Java本法方法的调用,具体会调用到JVM的如下方法:

JNIEXPORT jint JNICALL
Java_sun_nio_ch_ServerSocketChannelImpl_accept0(JNIEnv *env, jobject this,
                                                jobject ssfdo, jobject newfdo,
                                                jobjectArray isaa)
{
    jint ssfd = (*env)->GetIntField(env, ssfdo, fd_fdID);
    jint newfd;
    struct sockaddr *sa;
    int alloc_len;
    jobject remote_ia = 0;
    jobject isa;
    jint remote_port;

    NET_AllocSockaddr(&sa, &alloc_len);

    /*
     * accept connection but ignore ECONNABORTED indicating that
     * a connection was eagerly accepted but was reset before
     * accept() was called.
     */
    for (;;) {
        socklen_t sa_len = alloc_len;
        //这里就是进行了系统的调用,获取一个新的Socket的文件描述符,来和客户端的Socket进行对话
        //参考-----《Linux-UNIX系统编程手册(上、下册)中文版》第56.5.2章节和UNIX网络编程卷1:套接字联网API(第3版)第4.5和第4.6以及,https://blog.51cto.com/ticktick/779866。用这个newfd对应的Socket和客户端的Socket进行通信
        newfd = accept(ssfd, sa, &sa_len);
        if (newfd >= 0) {
            break;
        }
        if (errno != ECONNABORTED) {
            break;
        }
        /* ECONNABORTED => restart accept */
    }

    if (newfd < 0) {
        free((void *)sa);
        if (errno == EAGAIN)
            return IOS_UNAVAILABLE;
        if (errno == EINTR)
            return IOS_INTERRUPTED;
        JNU_ThrowIOExceptionWithLastError(env, "Accept failed");
        return IOS_THROWN;
    }

    (*env)->SetIntField(env, newfdo, fd_fdID, newfd);
    remote_ia = NET_SockaddrToInetAddress(env, sa, (int *)&remote_port);
    free((void *)sa);
    isa = (*env)->NewObject(env, isa_class, isa_ctorID,
                            remote_ia, remote_port);
    (*env)->SetObjectArrayElement(env, isaa, 0, isa);
    return 1;
}