virtual threads 无法直接支撑百万级长连接,因其仅优化 i/o 任务调度,不减少 socket fd、selector 线程或系统资源消耗;需配合 nio、合理线程池与 gc 配置才能落地。

不能直接靠 Virtual Threads 实现“每秒百万级长连接”的单机处理——这是常见误解。Virtual Threads 解决的是高并发 I/O 密集型任务的调度效率,不是 TCP 连接数的硬性突破。
为什么 newVirtualThreadPerTaskExecutor 无法撑住百万长连接
Virtual Threads 的轻量性体现在「任务粒度」,而非「连接生命周期」。一个长连接(如 WebSocket 或 HTTP/2 流)通常需持续持有 socket、缓冲区、会话状态等资源,这些由平台线程 + NIO 通道或直接由操作系统管理,Virtual Thread 只负责在其上调度阻塞式逻辑(比如 InputStream.read()),并不替代底层连接模型。
- 每个活跃长连接仍需至少一个
Selector线程(平台线程)轮询就绪事件;Virtual Thread 不接管 epoll/kqueue - 百万连接意味着百万 socket 文件描述符(fd),Linux 默认限制通常为 1024 或 65536,需手动调大
/proc/sys/fs/file-max和 ulimit - 连接保活、心跳、超时、断连清理等逻辑若全扔进 Virtual Thread,会因频繁挂起/恢复+ GC 压力反而拖慢响应
真正能跑通百万长连接的组合:Virtual Threads + NIO + 分层调度
关键不是“用虚拟线程代替 Netty”,而是让虚拟线程只做它擅长的事:在 I/O 就绪后,同步风格地处理业务逻辑。网络层仍需高性能 NIO 框架兜底。
- 用
java.nio.channels.ServerSocketChannel配合Selector接收连接,保持少量平台线程(如 2–4 个)处理网络事件 - 每个新连接注册到
Selector后,将其读写操作封装为Runnable,提交给Executors.newVirtualThreadPerTaskExecutor() - 业务逻辑中可放心调用
blockingQueue.take()、Files.readString()、JDBC driver(支持 Loom)等阻塞 API,JVM 自动挂起虚拟线程,不卡住 Selector 线程 - 避免在虚拟线程中做 CPU 密集型计算(如 JSON 序列化大对象),否则会挤占载体线程,导致其他虚拟线程饥饿
必须调优的 JVM 参数和陷阱
默认配置下,百万级虚拟线程可能触发 GC 飙升或调度抖动,不是代码写错了,是没对齐运行时假设。
- 启动时加
--enable-preview(JDK 21+ 必须),否则Thread.ofVirtual()抛UnsupportedOperationException - 设
-Djdk.virtualThreadScheduler.parallelism=8:匹配物理核数,避免过多平台线程争抢 CPU - 设
-Djdk.virtualThreadScheduler.maxPoolSize=2000:防止突发流量触发无限制扩容,引发线程工厂竞争 - 禁用
-XX:+UseZGC或-XX:+UseShenandoahGC:G1 在高分配速率下易 STW,ZGC 更适合虚拟线程场景(低延迟 GC) - 别用
Thread.sleep(Long.MAX_VALUE)模拟长连接保活——这会让虚拟线程长期挂起,占用栈帧且难监控;改用LockSupport.parkNanos()+ 定时器驱动
监控虚拟线程真实负载的三个关键指标
光看线程数没用。Virtual Thread 的“活跃数”和“挂起数”才是性能水位线。
- 用
jcmd <pid> VM.native_memory summary</pid>查看虚拟线程堆栈总内存(应稳定在 KB/线程量级,而非 MB) - 开启 JFR(Java Flight Recorder)录制,过滤事件
jdk.VirtualThreadParked和jdk.VirtualThreadUnparked,观察挂起频率是否异常高(说明 I/O 阻塞点太多或未对齐 NIO) - 通过
ManagementFactory.getPlatformMXBean(ThreadMXBean.class).getThreadCount()获取的是平台线程数;查虚拟线程要用jdk.jfr.consumer.RecordingStream或 VisualVM 的 “Virtual Threads” 标签页
真正卡住百万长连接的,从来不是线程创建成本,而是连接状态管理、内存碎片、GC 压力、文件描述符耗尽和网络栈缓冲区竞争。Virtual Threads 是把“业务逻辑调度”从瓶颈中解耦出来的工具,不是万能胶水。忽略 NIO 层设计、盲目堆虚拟线程,结果往往是吞吐没涨,GC 日志先刷屏。










