跳转到主内容
极星编程网:以代码为星,赴技术山海!

NIO 选择器的资源释放:分析 Selector.close() 如何批量注销所有已注册的 SelectionKey 变量

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

相关文章