java nio selector在linux上默认使用epoll,获得事件通知o(1)效率、连接数无硬上限、内核态交互成本降低三重红利,支撑单线程管理万级连接。

Java NIO 的 Selector 在 Linux 上从早期依赖 poll(或更早的 select)转向默认使用 epoll,不是简单换了个系统调用,而是吃到了内核演进带来的三重实质性红利:事件通知效率跃升、连接规模弹性扩展、以及资源开销显著收敛。这些红利直接支撑了现代高并发 Java 服务(如 Netty、Dubbo 网关、Kafka broker)单线程管理万级连接的能力。
就绪通知从 O(n) 扫描变成 O(1) 唤醒
select 和 poll 每次调用都必须把整个 fd 集合从用户态拷贝到内核态,并由内核线性遍历全部 fd 判断就绪状态——哪怕只有 1 个 socket 有数据,也要检查全部 5000 个。时间复杂度是 O(n),n 是注册总数。
epoll 把这个过程拆解为两步:
- 注册阶段用 epoll_ctl 将 fd 加入红黑树,O(log n) 插入,且只做一次;
- 等待阶段用 epoll_wait 直接从内核维护的就绪链表中取结果,返回的全是真正活跃的 fd,无需遍历。平均耗时稳定在微秒级,与注册总数无关。
这意味着:当你的 Selector 注册了 100 个或 10000 个 Channel,select() 调用的理论延迟几乎不变——这是 epoll 给出的最硬核的性能保障。
连接数不再受 FD 集合大小硬限制
select 使用固定长度的 bitset(通常是 1024 或 1024×N)描述 fd 集合,超出即报错;poll 虽用动态数组规避了 bitset 容量限制,但每次调用仍需传入并遍历全量数组,连接数一过万,CPU 就明显吃紧。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
epoll 没有这类硬上限:
- 红黑树支持动态增删,理论上只受限于内存和 ulimit -n;
- 内核只为活跃连接分配事件结构体,空闲连接零开销;
- 单个 epoll 实例轻松承载 10w+ 连接已成生产常态。
你不需要改代码、不需分 selector 实例,只要内核 ≥2.6.17 + JDK 7+,扩容就只是调大文件句柄数的事。
内核态与用户态交互成本大幅降低
select/poll 每次调用都要: - 用户态构造 fd_set / pollfd 数组; - 全量拷贝进内核; - 内核扫描后,再把修改后的结构拷贝回用户态; - 用户态再次遍历确认哪些 fd 就绪。
epoll 把关键状态保留在内核:
- fd 注册一次,长期驻留红黑树;
- epoll_wait 只需传入一个空 events 数组和最大期待数量;
- 内核直接填充就绪事件,只拷贝“有效数据”,无冗余传输;
- 用户态拿到的就是干净、可直接处理的就绪列表。
这不仅省 CPU,也减少缓存污染和上下文切换抖动——尤其在高频小包场景下,吞吐提升肉眼可见。
这些红利不是自动生效的。它要求你运行在 Linux 2.6.17+ 内核、JDK 7 或更高版本,且未通过 -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.PollSelectorProvider 主动降级。满足条件时,Selector.open() 会自动加载 EPollSelectorProvider,无缝对接 epoll 语义。不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










