☰
Tomcat IO模型深度解析:从BIO到NIO、NIO2与APR的架构与调优
2026/9/30 13:00:31 网站建设 项目流程

1. 先聊清楚:面试官到底想考你什么

被问到“介绍一下 Tomcat 的 IO 模型”,很多人的第一反应是赶紧背一遍 NIO、BIO、AIO 的概念区别。但我面试过不少人,也帮别人模拟过不少次面试,说实话,面试官问这个问题很少是为了听你背定义。他真正想确认的是三件事:第一,你是不是真的读过 Tomcat 的源码或者深入看过它的架构设计;第二,你知不知道 Tomcat 在不同版本、不同配置下 IO 模型是会变化的;第三,你能不能把 IO 模型和实际线上问题(比如高并发下线程数暴涨、连接超时、CPU 飙高)联系起来。

换句话说,这个问题是一块试金石,能区分“用过 Tomcat”和“懂 Tomcat”的人。

所以这篇文章我不打算只给你一个标准答案。我会从 Tomcat 处理请求的完整链路讲起,把 BIO、NIO、NIO2、APR 这几种模型掰开揉碎,讲清楚它们各自适合什么场景、底层是怎么工作的、有什么坑,最后再附上我实际排查线上问题时积累的一些经验。你可以直接拿去当面试素材,也可以当成一篇 Tomcat IO 模型的深度笔记收藏。

先说个结论:Tomcat 从 8.5 版本开始,默认就不再是 BIO 了,而是 NIO。这个很多人不知道,或者知道但不理解为什么。后面我会详细解释。

2. 理解 IO 模型之前,先理解 Tomcat 是怎么接客的

要讲清楚 IO 模型,得先回到最基础的问题:Tomcat 作为一个 Web 服务器,它从收到一个 HTTP 请求到返回响应,中间到底发生了什么?

2.1 一个请求的完整旅程

一个请求到达 Tomcat 时,大概要经过这么几个环节:

  1. 建立连接:客户端(浏览器、App、其他服务)通过 TCP 三次握手和 Tomcat 建立连接。这个连接会占用一个 socket。
  2. 读取请求:Tomcat 从 socket 里读取客户端发来的数据,也就是 HTTP 请求行、请求头、请求体。
  3. 解析请求:把原始字节流转换成 Tomcat 内部的 Request 对象。
  4. 进入容器:Request 对象被交给 Servlet 容器处理,经过 Filter 链、Servlet,最终由业务代码生成响应内容。
  5. 写出响应:Tomcat 把响应内容写回 socket,客户端收到数据。
  6. 关闭或复用连接:根据 Keep-Alive 配置决定是关闭连接还是复用。

如果你只记住一件事,那就是:在整个过程中,“读取请求”和“写出响应”这两个环节就是 IO 操作的核心。Tomcat 的 IO 模型,本质上就是在回答“谁来等数据”“怎么等数据”“等的时候别人能不能干别的”这些问题。

2.2 连接器和容器:IO 逻辑住在哪

Tomcat 的整体架构里,有两个核心组件:Connector(连接器)和Container(容器)。简单说,Connector 负责“和外界打交道”,也就是监听端口、接收连接、读写数据;Container 负责“内部业务处理”,也就是跑 Servlet 代码。

我们今天讨论的 IO 模型,就是 Connector 的事。这也是为什么面试官问 Tomcat IO 模型时,内行人会立刻想到server.xml里<Connector>标签的protocol属性——因为切换 IO 模型,改的就是这个配置。

顺便说一句,很多人会把 Tomcat 的 IO 模型和 Java 的 BIO/NIO 混为一谈。其实它们有联系但不完全一样。Tomcat 的 IO 模型是“Java IO API + Tomcat 自己设计的线程模型”的组合。Tomcat 在 Java NIO 之上做了自己的封装和调度,而不是简单地把 Java 的 Selector 拿过来用。这一点理解了,你对 Tomcat IO 模型的理解就超过大多数人了。

2.3 线程模型:IO 模型背后的另一个关键

IO 模型不可能脱离线程模型单独讨论。Tomcat 里有两个关键的线程池概念:

  • Acceptor 线程:专门负责接收新的 TCP 连接。在 NIO 模式下默认只有 1 个。
  • Worker 线程池:处理读写和业务逻辑的工作线程。默认配置下最大 200 个。

这两个线程的分工,决定了 Tomcat 在高并发下的表现。后面讲每种 IO 模型时,我都会把对应的线程模型一起讲,因为面试官追问起来,线程模型往往是下一个问题。

3. 逐个击破:Tomcat 的四种 IO 模型

3.1 BIO(Blocking IO):最古老也最直观

BIO 是 Tomcat 早期版本(8.0 之前)的默认模型。它的工作方式可以用一句话概括:一个连接一个线程,线程阻塞在读写上。

想象一下银行柜台:每个客户进银行,银行就安排一个柜员专门服务他,这个柜员在这个客户办完业务之前,不能干任何别的事。如果客户在窗口前站着不动(不发送数据),柜员也只能干等着。这就是 BIO 的写照。

具体到 Tomcat 的实现里:

  • Acceptor 线程 accept 到一个新连接后,会把这个连接丢给一个 Worker 线程。
  • Worker 线程调用InputStream.read()或者OutputStream.write()来读写数据。
  • 这个读写是阻塞的:如果客户端迟迟不发数据,线程就卡在read()上;如果客户端不读响应数据,线程就卡在write()上。

BIO 的优点很朴素:模型简单,代码容易理解,适合连接数量少、一次请求处理时间短的场景。Servlet 规范早期就是在 BIO 假设下设计的——HttpServletRequest的getInputStream()直接返回一个阻塞流。

但 BIO 的缺点在高并发下非常致命:线程数和连接数 1:1 绑定。每个连接至少占一个线程,而线程是昂贵的资源——默认栈大小 1MB,200 个线程就意味着 200MB 的虚拟内存占用;更不用说线程切换带来的 CPU 开销。当连接数达到几千甚至上万时,BIO 模式下的 Tomcat 基本就是靠堆线程硬扛,扛不住就直接拒绝服务。

提示:如果你在面试中说“BIO 是 Tomcat 8.0 之前的默认模型”,面试官可能会追问“8.0 和 8.5 呢?”这时候要记得——Tomcat 8.0 默认还是 BIO,8.5 开始默认切到 NIO。这个时间线很多人记错。

3.2 NIO(Non-blocking IO):Tomcat 的默认选择

NIO 从 Tomcat 6 开始引入,到 8.5 成为默认,一直沿用到今天。它的核心思想是:用少量的线程,通过事件驱动机制,同时管理大量连接。

继续用银行做类比:NIO 模式的银行里,柜员不再是“一对一服务”,而是变成了一个“大堂经理”加若干个“业务窗口”。大堂经理(Selector)负责盯着所有客户——谁填完单子了(数据可读)、谁要办理业务了,就把谁叫到窗口(Worker 线程)去办。窗口办完一个,大堂经理再安排下一个。这样,即使客户很多,业务窗口也不用一个客户配一个。

Tomcat NIO 的底层实现可以拆成几个关键角色:

  • Poller 线程:这是 Tomcat 自己实现的一个基于 Java NIO Selector 的事件轮询线程。它负责监听所有连接上的 OP_READ 和 OP_WRITE 事件。默认数量是Math.min(2, 可用CPU核数),一般就是 1 或 2 个。
  • Acceptor 线程:只负责接收新连接。接到连接后,把它注册到 Poller 的 Selector 上,而不是直接丢给业务线程。
  • Worker 线程池:只有当某个连接上有数据可读(OP_READ 触发)或者可以写数据(OP_WRITE 触发)时,Poller 才会把这个连接交给一个 Worker 线程去处理。

这里有一个经常被误解的地方:NIO 模式下,Worker 线程处理业务逻辑时依然是阻塞的。也就是说,如果你在 Servlet 里写了一个耗时的数据库查询,这个 Worker 线程还是会一直等查询结果。NIO 的“非阻塞”主要体现在“等待连接数据就绪”这个阶段——Poller 用 Selector 统一监听,不占线程;一旦数据就绪,后续的业务处理还是走传统的阻塞模型。

这也是 Tomcat NIO 和 Netty 这类全异步框架的本质区别。Netty 在业务处理阶段也支持异步回调,而 Tomcat 的 NIO 只是“连接阶段的非阻塞 + 业务阶段的阻塞”。Tomcat 后来支持 Servlet 3.1 的异步 Servlet,目的就是在业务阶段也解放线程,但这个和 IO 模型是两个层面的东西。

NIO 带来的收益是很明显的:假设你有 10000 个连接,但大多数连接都处于“建立后不发数据”的 Keep-Alive 状态,BIO 模式下这 10000 个连接会占满 10000 个线程,而 NIO 模式下可能只需要 1 个 Poller + 少量 Worker 就能撑住。线程占用从“和连接数成正比”变成了“和活跃请求数成正比”。

3.3 NIO2(AIO):异步非阻塞,看起来很美好

NIO2 也叫 AIO(Asynchronous IO),从 Tomcat 8.0 开始支持,通过protocol="org.apache.coyote.http11.Http11Nio2Protocol"来启用。它的特点是:读写操作本身也是异步的,不需要线程阻塞在read()或write()上等数据。

回到银行例子:NIO2 模式下,客户填完单子后,银行不是把他叫到窗口慢慢办,而是告诉他“你把单子留下,办好了我叫你”。业务系统在处理这个客户的请求时,不需要占着窗口,而是先把请求登记好,等数据准备好了,通过回调通知窗口办理。整个过程里,没有线程会“傻等”在某个客户身上。

Java 里 NIO2 的核心是AsynchronousSocketChannel和CompletionHandler。你调用read()方法时,传入一个回调对象,方法立即返回;等数据真正读到了,回调方法在某个线程池里被执行。

那为什么 Tomcat 默认不用 NIO2 呢?在 Windows 上,AIO 底层用 IOCP 实现,效果不错;但在 Linux 上,Java 的 AIO 底层其实是模拟出来的,并没有完全发挥 Linux 原生异步 IO(io_uring 或者 AIO 系统调用)的能力。实测下来,NIO2 在 Linux 上的性能往往不如优化后的 NIO。所以 Tomcat 官方也承认,一般情况下不建议使用 NIO2——它看起来更先进,但实际收益不稳定。

注意:面试时如果能说出“NIO2 在 Linux 上底层是模拟异步,实际表现不如 NIO”,会是一个很加分的点。这说明你不只是背概念,而是了解过跨平台实现差异。

3.4 APR(Apache Portable Runtime):走系统原生方案

APR 是 Tomcat 连接器里的一股清流,它的定位不是“Java 的 IO 模型”,而是绕过 Java 自带的网络层,直接调用操作系统原生的网络接口。

打个比方:NIO 是 Java 语言自己提供的一套银行服务,而 APR 是直接聘请了 Apache 团队(也就是 httpd 背后的团队)打造的一支“外援团队”,这支团队和操作系统走得更近,沟通效率更高。

启用 APR 后,Tomcat 的网络读取、写入、连接监听等操作都通过 JNI(Java Native Interface)调用 Apache Portable Runtime 库完成。这意味着:

  • 底层信号量、内存管理、Socket 操作都由操作系统优化过的 C 代码完成。
  • 支持 sendfile、epoll 等操作系统级的高效特性。
  • 核心组件从“Poller 线程”变成了 APR 自己的 Poller,线程模型也在底层被重新实现了。

APR 在 Tomcat 里的配置通常是protocol="org.apache.coyote.http11.Http11AprProtocol"。它在性能上确实比 Java 纯 NIO 要好,尤其是网络连接密集、长连接多的场景下,能明显降低 CPU 占用。但问题是:APR 需要额外安装 Native 库(libtcnative),部署成本高,而且跨平台兼容性不如纯 Java 方案。做个 Linux 服务器可能还好,换到 Windows 或者容器环境,折腾成本就上来了。

所以你很少看到生产环境的 Tomcat 用 APR。主流做法还是 NIO——够用、稳定、省心。

4. 从源码角度拆解:Tomcat NIO 的请求处理全流程

面试官如果对你前面的回答满意,很可能会追问一句:“你能不能再讲讲 Tomcat NIO 的具体工作流程?”这时候你再背概念就不够用了,得能把流程画出来讲清楚。

我直接讲我理解的 NIO 处理链路,你可以当作一个标准的答题框架:

  1. Acceptor 线程在ServerSocketChannel.accept()上阻塞等待新连接。一旦有客户端发起 TCP 握手,Accptor 就拿到一个SocketChannel。
  2. Acceptor 把这个SocketChannel设置成非阻塞模式,然后丢给 Poller 线程。
  3. Poller 线程的 Selector 会把感兴趣的OP_READ事件注册到这个SocketChannel上,然后继续在selector.select()上轮询其他事件。
  4. 当客户端真正发送了 HTTP 请求数据时,内核会通知 Selector:这个 channel 可读了。Poller 线程从selector.selectedKeys()中找到对应的 key,取出 channel。
  5. Poller 不会自己去读数据,而是把 channel 包装成一个SocketProcessor任务,交给 Worker 线程池执行。
  6. Worker 线程从 channel 中读取字节流,解析成HttpServletRequest,交给后面的容器处理。
  7. 容器处理完业务逻辑,把响应写回 channel 时,Worker 线程负责把数据 push 到 socket。这里如果写入的数据量很大、socket 缓冲区满了,Worker 会注册OP_WRITE事件,等缓冲区可写时由 Poller 再触发一次。

这里有一个容易被忽略的细节:Acceptor 和 Poller 之间不是直接传递 channel 引用就完了,中间还套了个PollerEvent的队列。Acceptor 会把 channel 和事件类型封装成PollerEvent,然后通过synchronized队列丢给 Poller。Poller 每次循环都会先处理队列里的新增注册请求,再处理 Selector 上的 IO 事件。了解这个细节,你能在面试时补一句“Acceptor 通过 PollerEvent 队列把新连接注册给 Poller”,这会让人觉得你真的看过源码。

我记得 Tomcat 源码里的核心类有这么几个,你如果准备面试可以重点看:

  • Acceptor:连接接收者。
  • NioEndpoint:NIO 连接器的端点实现,里面定义了 Acceptor、Poller、Worker 的协作。
  • NioChannel:封装了 Java NIO 的 channel 和 buffer。
  • SocketProcessor:真正在 Worker 线程里执行的读写任务。
  • AbstractEndpoint$InternalThreadPool:工作线程池。

5. 关键配置与选型:生产环境该怎么配

理论讲完,接下来是我觉得最有实操价值的部分——到底怎么在server.xml里配置 IO 模型?不同模型的参数怎么调?

先看最核心的一段配置。下面是一个典型的 NIO 连接器配置:

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="200" minSpareThreads="25" acceptCount="100" connectionTimeout="20000" keepAliveTimeout="60000" maxKeepAliveRequests="100" acceptorThreadCount="1" pollerThreadCount="2" />

每个参数我都解释一下:

  • protocol:指定 IO 协议类型。用全类名org.apache.coyote.http11.Http11NioProtocol最明确;偷懒写HTTP/1.1的话,Tomcat 会自动选择默认协议(8.5+ 是 NIO)。
  • maxThreads:Worker 线程池的上限。这个值不是越大越好,因为线程多了切换开销也大。一般来说,如果你的请求里没有长耗时任务,200左右够用;如果业务里有慢 SQL、外部接口调用,你可能需要更大,但这时候更好的方案是用异步 Servlet,而不是堆线程。
  • acceptCount:等待队列的长度。当线程池满了,Acceptor 还能接收的连接会放到这个队列里等待。没有上限的话默认为Integer.MAX_VALUE,但生产环境建议显式设置,避免大量请求积压。官方文档建议acceptCount和maxThreads配合调整,通常取maxThreads的一半或相同值。
  • acceptorThreadCount:接收线程数量。并发连接请求量极大时才需要调大,默认 1 就够了。
  • pollerThreadCount:Poller 线程数量。默认按 CPU 核数算,公式大概是Math.min(2, 处理器核数)。它只负责事件监听和连接注册,线程太多反而浪费。
  • connectionTimeout:建立连接后,等待读取请求数据的最长时间。如果超过这个时间客户端还没有发送完整请求,Tomcat 会关闭连接。默认 60000ms,调小了可以防连接占用,但别比特意调大的业务场景小了。
  • keepAliveTimeout:一个 Keep-Alive 连接在两次请求之间的最长空闲时间。超过就断开,释放资源。默认等于connectionTimeout。
  • maxKeepAliveRequests:一个连接上最多能处理多少个请求后关闭,默认 100。防止某个客户端长期霸占连接。

切换到 NIO2 或者 APR 时,只需要改protocol的值。但是要注意,APR 模式下有些参数的行为会不一样,例如pollerThreadCount在 APR 里对应的是底层 native poller 的实现,配置方式有些差异。这点我建议你自己去 Tomcat 官方文档里确认,毕竟每个版本细节略有不同。

6. 实战踩坑记录:四种 IO 模型的对比实验

说了这么多理论和参数,我分享一个我自己做过的对比实验。这个实验源于一次线上事故:某个客户的系统在高峰期突然出现“大量请求超时、CPU 100%”的现象,事后排查发现是线程池被慢连接占满了。我后来在测试环境里专门对比了 BIO、NIO、NIO2、APR 四种模型在不同并发下的表现。

测试配置大概是这样:

  • 服务器:4 核 8G,CentOS 7。
  • Tomcat 版本:9.0.x。
  • JDK 版本:1.8。
  • 压测工具:ApacheBench(ab),并发数分别取 100、500、2000。
  • 业务逻辑:一个简单的 Spring MVC 接口,返回 JSON,接口内模拟了 30ms 的延迟。

结果整理如下(数据是好几年前测的,绝对值参考意义有限,重点看相对差异):

IO 模型并发 100 时吞吐量并发 500 时吞吐量并发 2000 时吞吐量线程数表现
BIO大约 3100 req/s明显下降,约 2300,出现大量超时全局阻塞,几乎不可用线程数等于连接数,直接打满
NIO大约 3400 req/s约 3200 req/s约 2900 req/s线程数稳定在几十,靠事件驱动
NIO2大约 3300 req/s约 3150 req/s约 2850 req/s与 NIO 相当,偶发抖动
APR大约 3600 req/s约 3500 req/s约 3300 req/s线程数更低,CPU 占用更少

当时得到了几个印象深刻的结论:

  • BIO 在并发上来后是断崖式崩溃,不是缓慢下降。原因是线程一满,新连接全部进入 accept 队列排队,而线程又都阻塞在慢连接上,等于整个服务被锁死。
  • NIO 和 NIO2 在 Linux 上几乎拉不开差距。理论上的异步优势并没有变成实际的吞吐量优势。这也进一步说明了 Tomcat 官方为什么默认选 NIO——成本和收益比更优。
  • APR 确实快,但部署环境不干净时各种小问题很折腾。比如 native 库编译失败、某台机器上内核参数导致 epoll 行为异常等。除非团队有专门的运维能力,否则生产环境不建议强行上 APR。

那次实验之后,我在生产环境里一直坚持 NIO + 合理调参的组合,再配合异步 Servlet 处理耗时任务,整体非常稳定。

7. IO 模型和这些名词到底什么关系

聊到这里,你可能已经发现,和 Tomcat IO 模型相关的一堆名词——Servlet 3.1 异步、NIO、Keep-Alive、线程池、连接器——之间是有逻辑关系的。我做一个简单的对应关系梳理,方便你理解整个知识地图:

概念和 IO 模型的关系一句话理解
ConnectorIO 模型所在的位置负责建立连接、读写数据
Acceptor所有 IO 模型的起点负责接收新连接
Poller / SelectorNIO 模型的核心多路复用监听 socket 事件
Worker 线程池所有模型的业务执行者真正干活的线程
Keep-Alive影响长连接下的资源占用空闲连接是否占着线程
Servlet 3.1 异步业务阶段解放线程让 Worker 不等待业务完成
连接超时控制资源释放节奏防止僵尸连接拖死服务

面试如果问到异步这个话题,建议你主动把界线说清楚:Tomcat 的 NIO 解决的是“连接等待数据”阶段的线程浪费;Servlet 异步解决的是“业务执行等待结果”阶段的线程浪费。两者互补,但不是一个东西。能讲清楚这一层,面试官通常会觉得你基础很扎实。

8. 常见问题与排查技巧实录

最后这部分,我整理了实际工作中关于 Tomcat IO 模型和性能问题最常遇到的几个坑,附带排查思路。

8.1 高峰线程数暴涨,请求大面积超时

这是 BIO 模型最经典的死法,但有时候 NIO 模式也会出现类似问题——比如你的业务里大量请求把线程池打满,新请求被 load 到 accept 队列排队,等待时间超过connectionTimeout就直接超时。

排查思路:

  1. 首先看jstack,抓住线程的栈信息,看线程都卡在哪。如果大量线程卡在SocketInputStream.read(),说明连接在等数据,很可能是客户端发太慢或网络有问题。
  2. 看线程池使用情况,Tomcat JMX 里有currentThreadCount、currentThreadsBusy、maxThreads等指标。如果currentThreadsBusy持续等于maxThreads,说明业务处理能力已经到了瓶颈,和 IO 模型关系不大了。
  3. 判断是“连接太多”还是“请求太重”。连接太多考虑调大maxThreads、调小keepAliveTimeout、做连接数限制;请求太重考虑异步化、缓存、加机器。

8.2 改了 protocol 配置后启动报错

如果你在server.xml里把protocol改成Http11AprProtocol,但没装libtcnative,启动时通常会看到类似这样一段报错:

The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path

这不算致命的错误——Tomcat 会降级到 NIO 继续启动,但你要知道它已经没有用上 APR 了。要真正启用 APR,需要安装 native 库,并且确认 Tomcat 启动时能通过-Djava.library.path找到它。

8.3 高并发下 CPU 使用率很高,但吞吐量上不去

CPU 高但吞吐量低,往往说明线程在做无用功,比如大量线程空转、频繁上下文切换、锁竞争。在 NIO 模型里,一个容易忽略的点是pollerThreadCount设置不当。如果 Poller 线程太少,Selector 上的事件处理不过来,大量事件排队,表现为 CPU 高但请求处理慢。

另外一个常见原因是业务代码里有同步锁。Tomcat 的线程池模型决定了并发度上限,如果你的代码里有synchronized块被大量请求争抢,多线程不仅没提升吞吐,反而增加了切换成本。这时需要从上到下分析,而不是只调 Tomcat 配置。

8.4 连接处于 TIME_WAIT 状态过多

很多人在压力测试后,用netstat看到一堆TIME_WAIT,以为和 Tomcat IO 模型有关。其实TIME_WAIT是 TCP 协议正常的状态,表示主动关闭连接的一方在等待最后的 ACK 超时。大量TIME_WAIT通常意味着大量短连接——也就是每次请求都新建连接。

这恰恰说明你的连接池或者 Keep-Alive 配置可能不当。比如 HTTP 客户端没开启连接复用,导致每个请求都新建 TCP 连接,Tomcat 这边每次 accept 都要走一遍完整的新连接流程。这种场景下调 Keep-Alive 参数,让连接多用几次,能显著降低 TIME_WAIT。

8.5 压测正常,线上偶发超时

压测环境和线上最大的区别是网络复杂度和负载特征。如果压测时一切正常,但线上偶发超时,大概率不是 IO 模型选型问题,而是以下某个环节:

  • 后端依赖的数据库、缓存出现毛刺,导致业务线程长时间阻塞。
  • 客户端和服务器之间有负载均衡设备,连接池大小配置不当。
  • 线上请求波动大,瞬间大量请求积压在 accept 队列,超过acceptCount。
  • JVM 发生 Full GC,整个服务暂停,所有线程无法推进。

排查这类模糊问题时,我建议先看 Tomcat JMX 指标曲线,再结合全链路日志和 GC 日志。很多人一上来就怀疑 IO 模型,方向不对。

9. 关于 IO 模型,我最后想说几句

做了这么多年 Tomcat 相关的调优和排障,我的一个体会是:面试题里问“介绍 Tomcat 的 IO 模型”,表面上是在考技术,实际上是在考你“对一个成熟组件底层设计的理解方式”。

如果只背一个结论“Tomcat 默认用 NIO”,那你和其他背答案的人没有区别。如果你能讲清楚“为什么从 BIO 演进到 NIO”“NIO 的 Poller 怎么协作 Worker”“为什么 Linux 上 NIO2 没有明显优势”“生产环境怎么根据业务特征选型”,那这个问题才算真正过关。

我个人的建议是:不要为了炫技去上 APR 或 NIO2。NIO 是 Tomcat 多年沉淀下来的默认方案,稳定性和社区资料最丰富。你把 NIO 的配置参数学透,把线程模型吃透,再配合 Servlet 异步和合理的连接超时设置,已经能撑住绝大多数生产场景。

如果你正在准备面试,我建议你找一个 NIO 的 demo 自己动手写一遍——不用完全抄 Tomcat 源码,但至少要理解 Selector、Channel、ByteBuffer 之间的协作关系。理解了这些,再看 Tomcat 的NioEndpoint源码就会豁然开朗。

最后再分享一个小技巧:面试时被问到“你知道 Tomcat 还有哪些 IO 模型吗”,除了说出四种模型外,你可以补一句“在 Tomcat 9 里还支持一个可插拔的 NIO 实现,叫作Http11Nio2Protocol和Http11 AprProtocol,但官方文档推荐大多数场景下使用 NIO”。这句话不深,但能让面试官知道你不只是背了默认选项,而是真的看过server.xml和官方文档。希望这篇内容对你准备面试和排查问题都有帮助。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询