Tomcat 8+默认启用NIO,但实际是否生效需以启动日志中Http11NioProtocol为准;若显示Http11Protocol或Http11AprProtocol,则NIO未生效,常见原因包括server.xml未显式配置protocol、旧版Tomcat默认回退BIO、Docker环境参数干扰或APR库意外触发切换。

Nio模式在Tomcat 8+中默认启用,但若未显式配置或运行在旧版(如Tomcat 7)、容器环境、或被意外覆盖,实际仍可能退化为BIO,导致线程暴涨、连接堆积、connection refused频发——这不是并发不够,是IO模型没对上。
确认当前是否真正在用NIO
别只看Tomcat版本,得看实际加载的Endpoint类。启动日志里搜这行:
org.apache.coyote.http11.Http11NioProtocol
如果看到的是Http11Protocol(BIO)或Http11AprProtocol(APR),说明NIO没生效。常见原因包括:
-
server.xml中<connector></connector>没写protocol属性,且系统没自动 fallback 到NIO(比如Tomcat 7 + Linux无APR库时,默认仍是BIO) - 用了
HTTP/1.1字符串值,而该字符串在Tomcat 7中会触发BIO,在Tomcat 8+才映射为NIO - Docker镜像或启动脚本里硬编码了
-Dorg.apache.catalina.connector.RECYCLE_FACADES=true等参数,干扰了协议自动识别
强制启用NIO并配对关键参数
直接在server.xml的<connector></connector>中指定完整协议类名,不依赖默认行为:
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="200"
minSpareThreads="25"
maxConnections="10000"
acceptCount="100"
connectionTimeout="3000"
redirectPort="8443" />重点不是堆高maxThreads,而是让maxConnections与NIO特性匹配:
-
maxConnections应设为远高于maxThreads(比如10倍),因为NIO下线程不绑定连接,一个线程可轮询处理成百上千个空闲keep-alive连接 -
acceptCount只是ServerSocket backlog队列长度,设太大(如>200)反而会掩盖后端处理瓶颈,建议保持默认或设为maxThreads * 2 -
connectionTimeout必须显式调低(3000比默认20000更合理),否则长连接空转会占满maxConnections却无实际请求
避免NIO被APR或JVM参数意外覆盖
APR优先级高于NIO,只要检测到libtcnative-1.so和apr库,Tomcat 8+就会自动切APR——但这未必是你想要的:
- APR需要额外安装
apr、apr-util、tomcat-native,在Alpine等精简镜像里极易缺失,导致启动失败或静默回退到BIO - JVM参数
-Djava.library.path若指向了含APR库的路径,即使没配Http11AprProtocol也会触发切换 - 检查
logs/catalina.out是否有Loaded APR based Apache Tomcat Native library,有则确认是否真需APR;不需要就删掉lib/libtcnative-1.so或清空java.library.path
线程池必须显式声明并与Connector绑定
NIO本身不解决线程调度,它只把IO事件分发给线程池。不配<executor></executor>,Tomcat会用内置默认线程池,但无法复用、难监控、参数不可调:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="200" minSpareThreads="25" prestartminSpareThreads="true" />
<p><Connector executor="tomcatThreadPool"
protocol="org.apache.coyote.http11.Http11NioProtocol"
... />关键点:
-
prestartminSpareThreads="true"避免冷启动时大量线程创建开销 - 多个
<connector></connector>(如HTTP+HTTPS)可共用同一<executor></executor>,减少线程资源碎片 - 不要把
maxThreads设成CPU核心数×100——NIO线程主要做轻量解析和转发,200~400足够应对万级QPS,再高反而引发上下文切换损耗
真正卡住高并发的,往往不是“要不要NIO”,而是maxConnections和connectionTimeout这对组合没按NIO语义调——它们决定了连接生命周期如何被管理,而不是线程数量。漏掉这点,哪怕协议写对了,也还是BIO式排队。

















