Selector.close()会同步取消所有SelectionKey并释放fd等资源:先置CLOSED状态,再遍历注销key、解除引用、通知内核移除监听;cancel不立即释放fd,完整释放需key.cancel()、channel.close()、selector.close()三者俱全。
Selector.close()
会同步遍历并取消所有已注册的
,触发其内部的注销逻辑,最终释放底层文件描述符(fd)和相关 JVM 资源。
close() 的核心行为:批量取消 + 状态清理
调用
时,NIO 并不会直接逐个调用每个
,而是:
将 selector 置为
状态,后续操作(如
、
)会立即抛出
遍历内部 key 集合(通常是
和未返回的已注册 key),对每个 key 执行等效于
的注销流程
清除 key 与 channel、selector 的双向引用,解除 channel 内部对 key 的持有
通知底层系统(如 epoll/kqueue)移除该 fd 的监听事件,释放内核侧资源
SelectionKey.cancel() 是注销的关键环节
无论是否显式调用
,
内部都会让每个 key 进入“已取消”状态:
key 的
立即返回
key 从 selector 的已注册 key 集合中被逻辑移除(实际从集合中删除通常延迟到下一次
或 close 期间)
channel 不再通过该 key 接收就绪事件,也不会再向 selector 注册新 interest ops
注意:cancel 本身不立即释放 fd,真正释放依赖于 selector 的 close 或底层 poller 的 cleanup 阶段。
资源释放的完整链条
一个 SelectionKey 关联的资源最终释放需满足三个条件:
key 被 cancel(由 close 触发)
channel 被关闭(或显式调用)
selector 关闭(触发 fd 从 epoll 实例中 del)
三者缺一不可。例如,仅 cancel key 而不 close selector,fd 仍保留在 epoll 中;仅 close selector 但 channel 未关,fd 可能延迟回收(取决于 GC 和 finalizer,不推荐依赖)。
实际开发中的注意事项
避免常见误用:
不要在
循环中仅遍历并 cancel key,却忘记 close selector —— 可能导致 fd 泄漏
不要在 key 已 cancel 后继续调用
,会抛
关闭 selector 前,确保没有其他线程正在调用其
、
等方法,否则可能引发并发异常
使用 try-with-resources 是最安全的方式:
SelectionKeySelector.close()SelectionKey.cancel()CLOSEDselect()keySet()ClosedSelectorExceptionselectedKeyskey.cancel()key.cancel()Selector.close()isValid()falseselect()key.channel().close()select()key.interestOps(...)CancelledKeyExceptionselect()register()try (Selector sel = Selector.open()) { ... }