根本原因是虚拟线程未真正启用:tomcat需显式配置virtualthreadpertaskexecutor并绑定到connector,否则仍走平台线程;spring配置spring.threads.virtual.enabled=true不影响tomcat线程模型。

虚拟线程启用后Tomcat吞吐量没提升,甚至下降了
根本原因不是虚拟线程不行,而是默认没生效——Tomcat 10.1.15+ 虽支持虚拟线程,但必须显式开启,且仅对 Executor 类型为 VirtualThreadPerTaskExecutor 的线程池起作用。老项目沿用 ThreadPoolExecutor 或未配置自定义 Executor,虚拟线程完全不参与请求处理。
- 检查
server.xml中的<executor></executor>配置:必须指定className="org.apache.catalina.core.VirtualThreadPerTaskExecutor",不能只改maxThreads - Spring Boot 3.2+ 用户注意:
spring.threads.virtual.enabled=true仅影响 Spring 自身调度(如@Async),**不影响 Tomcat 连接器线程模型** - 启用后用
jstack <pid></pid>观察线程名,出现大量VirtualThread-前缀才表示真正跑起来了
HTTP连接器仍卡在平台线程上,虚拟线程没接收到请求
即使 Executor 改了,如果 Connector 没绑定它,请求进来还是走传统线程池。Tomcat 的连接器和执行器是解耦的,必须手动关联。
-
Connector标签里必须加executor="yourVirtualExecutorName"属性,否则默认使用内置平台线程池 - 别漏掉
useBodyEncodingForURI="true"等老配置——某些编码相关逻辑在虚拟线程下会触发IllegalStateException: Thread is not a virtual thread - 禁用
keepAliveTimeout或设为极小值(如100),避免长连接阻塞虚拟线程调度器
吞吐量测试时看到大量 java.lang.OutOfMemoryError: unable to create native thread
虚拟线程本身不占栈内存,但每个都依赖一个平台线程作为载体(carrier thread)。默认 carrier pool 大小受限于 OS 线程数上限,压测并发高时容易耗尽。
- 启动参数加
-XX:ActiveProcessorCount=8(按物理核数设),避免 JVM 自动取逻辑核数导致 carrier 过多 - 设置系统级限制:
/proc/sys/kernel/threads-max和ulimit -u至少 > 2×预期并发数 - 避免在虚拟线程里调用
Thread.sleep()、Object.wait()或阻塞 I/O——这会让 carrier 被长期占用,等效于退化成平台线程
数据库连接池成为新瓶颈,虚拟线程反而放大等待时间
虚拟线程让应用层并发能力飙升,但若数据库连接池(如 HikariCP)仍用固定 maximumPoolSize=20,所有虚拟线程会挤在少数连接上排队,RT 暴涨,吞吐不升反降。
-
HikariCP必须配合spring.datasource.hikari.maximum-pool-size调大(建议 ≥ 平台线程数 × 2),但别盲目设到几百——连接数受 DB 服务端限制 - 优先用异步驱动(如
postgres-reactive或mysql-async),让虚拟线程在 I/O 时不阻塞 carrier - 观察
HikariPool-1 housekeeper日志:若频繁打印Connection leak detection triggered,说明虚拟线程生命周期管理不当,连接未及时归还
真实瓶颈往往不在虚拟线程本身,而在它暴露出来的旧组件容量问题——比如 DNS 解析超时、第三方 HTTP 客户端没切异步、日志框架同步刷盘。这些地方一旦卡住,虚拟线程只会更快地堆积等待。











