← 返回题目列表

Tomcat 支持哪些 IO 模型(连接器)?BIO、NIO、NIO2 和 APR 有什么区别?

高频 中等 第 9 / 23 题 更新于 2026/08/03
ConnectorIO模型NIOAPR

简化版

Tomcat 用 Connector(连接器) 接收网络连接、处理 IO,它支持多种 IO 模型(由 ProtocolHandler 决定):① BIO(同步阻塞,一连接一线程,性能差,Tomcat 8.5 起已移除);② NIO(同步非阻塞,基于 Java NIO 的多路复用,用少量线程管大量连接,现在的默认);③ NIO2(AIO)(异步 IO,基于 Java AIO,Linux 上底层仍是 epoll 模拟、优势不明显,用得少);④ APR(Apache Portable Runtime,基于本地 C 库直接调操作系统的 epoll/sendfile,性能最高,但要单独装本地库)。核心演进:BIO 因性能差被淘汰 → NIO 成为默认(多路复用扛高并发)→ APR 追求极致性能(本地库)。一句话:现在 Tomcat 默认用 NIO,配合它的 Acceptor/Poller/Worker 线程模型扛高并发

详细版

Tomcat 的四种 IO 模型(Connector 类型)

模型底层特点现状
BIO传统阻塞 IO一连接一线程,并发差Tomcat 8.5 起移除
NIOJava NIO(多路复用)少量线程管大量连接默认
NIO2Java AIO(异步)异步回调,但 Linux 上是 epoll 模拟可选,用得少
APRApache 本地 C 库直接用 OS 的 epoll/sendfile,性能最高需装本地库,追求极致性能时用

Connector 的组成(Coyote 组件):

Connector 内部核心:ProtocolHandler
  ProtocolHandler = Endpoint(处理网络连接和 IO)+ Processor(解析协议,如 HTTP)

不同 IO 模型对应不同的 ProtocolHandler 实现:
  NIO:Http11NioProtocol(默认)
  NIO2:Http11Nio2Protocol
  APR:Http11AprProtocol

配置(server.xml):
  <Connector protocol="org.apache.coyote.http11.Http11NioProtocol" .../>
  或简写 protocol="HTTP/1.1"(Tomcat 自动选择,一般是 NIO)

为什么 BIO 被淘汰

BIO:一个连接一个线程,线程从"接受连接"到"处理完请求"全程占用
  → 1 万并发连接 = 1 万个线程 → 内存爆炸、上下文切换开销大
  → 而且很多连接在"等数据"(keep-alive 长连接空闲时),线程白白占着
NIO:用多路复用(Selector/epoll)监听大量连接,少量线程处理就绪的
  → 几十个线程管几万连接 → 高并发下完胜 BIO
→ 所以 Tomcat 8.5 直接移除了 BIO,默认 NIO

⚠️ Tomcat 8.5+ 已经移除了 BIO 连接器——所以现在讨论「Tomcat 的 IO 模型」实际就是 NIO(默认)、NIO2、APR 三选一,BIO 只是历史。而且 NIO 是绝对主流的默认选择——它用「Acceptor 接连接、Poller 用 Selector 轮询就绪、Worker 处理业务」的线程模型(就是 Reactor 模式),在绝大多数场景下性能足够好。APR 虽然更快,但要额外安装本地库(libtcnative)、增加运维复杂度,只在对性能极度敏感时才用。

完整版教学

一、Connector 的职责:连接与 IO 的入口

Tomcat 的架构分「Connector(连接器)+ Container(容器)」——Connector 负责「网络层」(接受连接、读写字节、解析协议),Container 负责「Servlet 层」(把请求交给对应 Servlet 处理)。IO 模型的选择就发生在 Connector 这一层:

一个 HTTP 请求进来:
  Connector(IO 模型在这里):
    - 接受 TCP 连接
    - 用某种 IO 模型(BIO/NIO/APR)读取字节
    - 把字节解析成 HttpServletRequest 对象
  → 交给 Container(Engine→Host→Context→Wrapper)→ 最终到 Servlet

Connector 的核心组件叫 Coyote,它内部的 ProtocolHandler 决定了「用哪种 IO 模型 + 处理什么协议」。IO 模型的差异,本质是「Connector 怎么读写网络数据」的差异——是阻塞地一连接一线程读(BIO),还是用多路复用少线程管多连接(NIO),还是用本地库直接调 OS(APR)。理解「IO 模型是 Connector 层的事、决定了 Tomcat 怎么处理网络连接」,就抓住了这道题的定位——它讲的是 Tomcat 的「网络处理能力」。

二、BIO:为什么被淘汰

BIO(Blocking IO,同步阻塞)是最早的模型,也是最简单的——一个连接分配一个线程,从头管到尾

BIO 模型:
  每接受一个连接 → 分配一个线程处理它的整个生命周期
  这个线程要:读请求(阻塞等数据)→ 处理 → 写响应(阻塞等写完)→ 处理下一个请求(keep-alive)

问题:
  ① 线程数 = 连接数:1 万连接 = 1 万线程 → 内存爆炸(每线程约 1MB 栈)+ 上下文切换开销
  ② 线程大量空等:HTTP keep-alive 下连接空闲时,线程还占着(等下一个请求)却没事干
  ③ 无法扛高并发:线程是有限资源,连接一多就耗尽

BIO 的致命缺陷是「线程和连接一比一绑定」——在「连接多但大部分空闲」的 Web 场景(尤其 keep-alive 长连接)下,大量线程被空闲连接占着,浪费严重、扛不住高并发。这就是经典的 C10K 问题(一万连接就崩)。所以 Tomcat 8.5 直接移除了 BIO 连接器,它只剩历史意义。理解「BIO 因『一连接一线程』扛不住高并发而被淘汰」,就理解了为什么要有 NIO。

三、NIO:默认的多路复用模型

NIO(Non-blocking IO,同步非阻塞)是 Tomcat 现在的默认模型,核心是用 IO 多路复用(Selector/epoll)让少量线程管理大量连接

NIO 模型(就是 Reactor 模式):
  不再一连接一线程,而是用少量线程 + Selector 监听所有连接的事件
  Tomcat NIO 的三类线程:
    Acceptor:接受新连接(少量,通常 1 个)
    Poller:用 Selector 轮询,找出"就绪"(有数据可读)的连接
    Worker(线程池):处理就绪连接的业务(真正干活)

关键:连接空闲时不占线程(在 Selector 上"挂着")
  只有"就绪"的连接才分配 Worker 处理 → 少量线程管几万连接

NIO 相比 BIO 的突破是「解耦了连接数和线程数」——连接空闲时不占线程(挂在 Selector 上),只有有数据的连接才分配线程处理。所以几十个线程能管几万个连接,高并发下完胜 BIO。这本质就是 Reactor 模式(多路复用 + 事件驱动),和 Netty 的线程模型同源。Tomcat 的这三类线程(Acceptor/Poller/Worker)配合 maxThreadsmaxConnectionsacceptCount 等参数决定并发能力。NIO 是绝大多数场景的最优选择——性能足够好、无需额外依赖,所以是默认。理解「NIO = 多路复用 + Reactor + 少线程管多连接」,就理解了它为什么取代 BIO 成为默认。

四、NIO2(AIO):异步但优势不明显

NIO2 基于 Java AIO(异步 IO),理论上更先进——它是「完成通知」而非「就绪通知」(内核帮你读写完成后回调,而不是通知你「可以读了」你自己读):

NIO(同步非阻塞):Selector 通知"可以读了" → 你自己读(读的动作占线程)
NIO2/AIO(异步):发起读后不管 → 内核读完了回调通知你(读的动作内核做)

看起来 NIO2 更好(连读的动作都省了),但实际:
  在 Linux 上,Java AIO 底层是用 epoll 模拟的(Linux 没有成熟的原生异步 IO)
  → 并没有用真正的内核异步能力,性能不比 NIO 好、还多一层封装
  → 只有 Windows 的 IOCP 才是真异步,但服务器多用 Linux

所以 NIO2 在 Tomcat 里存在但用得少——它的理论优势(真异步)在 Linux 服务器上兑现不了(底层还是 epoll 模拟),性能和 NIO 差不多甚至更差。这和「为什么 Netty 不用 AIO 而用 NIO」是同一个原因:Linux 缺乏成熟的异步 IO,AIO 是假异步。所以实践中 Tomcat 极少用 NIO2,默认还是 NIO。理解「NIO2 是 AIO、理论先进但 Linux 上无优势」,就知道它为什么不是主流。

五、APR:追求极致性能的本地库

APR(Apache Portable Runtime)是性能最高的模型,但它不用 Java 的 IO,而是基于本地 C 库直接调用操作系统的底层能力

APR 模型:
  用 Apache 的本地库(libtcnative,C 写的)
  直接调用操作系统的 epoll、sendfile(零拷贝)等系统调用
  → 绕过 JVM 的一些开销,直接用 OS 最高效的机制

优势:
  性能最高(本地库 + 直接用 OS 的 epoll/sendfile 零拷贝)
  尤其在处理静态资源、SSL 时优势明显(本地库处理更快)

代价:
  ① 要单独安装本地库(libtcnative + OpenSSL),和操作系统绑定
  ② 增加部署/运维复杂度(本地库版本、平台兼容)
  ③ 出问题更难排查(C 库层面)

APR 是「用本地库换极致性能」——它绕过 JVM 直接用 OS 的 epoll、sendfile(零拷贝),比纯 Java 的 NIO 更快,尤其在静态资源和 HTTPS 场景。但代价是要装本地库、增加运维复杂度、平台绑定。所以 APR 只在「对性能极度敏感、且能接受额外运维成本」时用(如高流量的静态资源服务、SSL 密集场景)。绝大多数应用用 NIO 就够了,不必上 APR。理解「APR = 本地库 + 极致性能但有运维代价」,就知道它是「特殊场景的性能优化」,不是默认。

六、选型与演进总结

Tomcat IO 模型的演进和选型:

演进脉络:
  BIO(一连接一线程,扛不住高并发)
    ↓ 被淘汰(Tomcat 8.5 移除)
  NIO(多路复用,少线程管多连接,Reactor 模式)← 现在的默认
    ↓ 追求极致性能
  APR(本地库直调 OS epoll/sendfile,最快但要装本地库)

选型建议:
场景选择
绝大多数应用NIO(默认,性能足够、无额外依赖)
对性能极度敏感、静态资源/SSL 密集APR(本地库,最快,但要装 libtcnative)
追新的异步模型NIO2(但 Linux 上无优势,不推荐)
老旧需求BIO(已移除,不可用)

核心结论:Tomcat 现在默认 NIO,绝大多数场景 NIO 就是最优解——它用 Reactor 模式(多路复用 + Acceptor/Poller/Worker 线程)扛高并发,性能足够好且无需额外依赖。APR 是「性能极致党」的选择(有运维代价),NIO2 因 Linux 上无优势而少用,BIO 已成历史。面试答这题的关键是讲清「BIO 因一连接一线程被淘汰、NIO 用多路复用成默认、APR 用本地库追极致、NIO2 因假异步少用」这条演进和选型逻辑。

记忆钩子:「Tomcat Connector 的 IO 模型:BIO(一连接一线程扛不住高并发,8.5 移除)、NIO(多路复用 Reactor,少线程管多连接,默认)、NIO2(AIO 异步但 Linux 上 epoll 模拟无优势,少用)、APR(本地库直调 OS epoll/sendfile 最快但要装 libtcnative);由 ProtocolHandler 决定;默认 NIO 够用、APR 追极致、NIO2 别用」

七、常见误区与追问

  • 误区:Tomcat 现在还能用 BIO 连接器。 Tomcat 8.5 起已移除 BIO——因为「一连接一线程」扛不住高并发;现在是 NIO(默认)、NIO2、APR 三选一。
  • 误区:NIO2(AIO)比 NIO 性能好。 在 Linux(服务器主流)上 Java AIO 是用 epoll 模拟的假异步,性能不比 NIO 好、还多一层封装;只有 Windows 的 IOCP 才是真异步。
  • 误区:APR 是纯 Java 实现的更优 NIO。 APR 基于本地 C 库(libtcnative),直接调 OS 的 epoll/sendfile,不是 Java IO;性能最高但要单独安装本地库、有运维代价。
  • 误区:IO 模型选 APR 就一定更好。 APR 更快但要装本地库、增加运维复杂度、平台绑定;绝大多数应用用 NIO 就够,只有对性能极度敏感(静态资源/SSL 密集)才值得上 APR。
  • 追问:Tomcat 的 NIO 模型和 Reactor 有什么关系? Tomcat NIO 就是 Reactor 模式——用 Acceptor 接连接、Poller 用 Selector 轮询就绪连接、Worker 线程池处理业务,多路复用让少线程管大量连接,和 Netty 的线程模型同源。
  • 追问:为什么 Tomcat 默认用 NIO 而不是 APR? NIO 是纯 Java、无需额外依赖、性能足够好,适合绝大多数场景;APR 虽更快但要装本地库、增加运维复杂度、平台绑定,只在性能极致场景才值得,所以默认选更通用的 NIO。
  • 追问:Connector 里的 ProtocolHandler 是什么? 它决定「用哪种 IO 模型 + 处理什么协议」,由 Endpoint(处理网络连接和 IO)+ Processor(解析协议如 HTTP)组成;NIO 对应 Http11NioProtocol、APR 对应 Http11AprProtocol。

八、加强记忆

Tomcat 用 Connector(连接器,核心是 Coyote 的 ProtocolHandler 处理网络连接和 IO,IO 模型的选择就在这一层。四种模型的演进和选型:① BIO(同步阻塞,一连接一线程,keep-alive 下大量线程被空闲连接占着、扛不住高并发即 C10K,Tomcat 8.5 起已移除);② NIO(同步非阻塞,基于 IO 多路复用(Selector/epoll)+ Reactor 模式——Acceptor 接连接、Poller 轮询就绪、Worker 处理,少线程管大量连接、解耦了连接数和线程数,是现在的默认);③ NIO2(AIO)(异步,「完成通知」而非「就绪通知」,但 Linux 上是 epoll 模拟的假异步、无优势,少用,同 Netty 不用 AIO 的原因);④ APR(基于本地 C 库 libtcnative,直接调 OS 的 epoll/sendfile 零拷贝,性能最高尤其静态资源/SSL,但要装本地库、有运维代价、平台绑定)。选型:绝大多数用 NIO(默认、够用、无依赖),性能极致才上 APR,NIO2 因假异步别用,BIO 已成历史。一句话「BIO 一连接一线程被淘汰、NIO 多路复用 Reactor 成默认、NIO2 假异步少用、APR 本地库追极致,默认 NIO 就够」。