
Java 高并发优化不能简单理解为“把线程数调大”或“换成虚拟线程”。真正有效的方案,需要同时观察请求模型、线程调度、连接池容量、JVM 内存、废品回收、容器资源限制以及下游系统承载能力。虚拟线程解决的是“等待期间占用平台线程”的问题,而不是让 CPU 计算本身变快。只有先确认应用的主要瓶颈属于网络、磁盘、数据库等阻塞等待,才适合通过虚拟线程获得明显收益。
一、先建立正确的并发模型
传统平台线程通常对应操作系统线程,创建和切换成本较高,因此 Web 应用常使用固定大小的线程池。当请求中包含数据库查询、远程调用或文件操作时,平台线程会在等待期间被占用,线程池很快达到上限。虚拟线程则由 JVM 调度到少量平台线程上执行,遇到可识别的阻塞操作时会暂时卸载,让载体线程处理其他任务。这样可以用更低的线程资源支撑大量并发连接。
但虚拟线程并不等价于无限并发。每个请求仍会消耗堆内对象、请求上下文、ThreadLocal 数据、网络连接和下游服务配额。如果数据库连接池只有二十个连接,创建十万个虚拟线程也不会让数据库同时处理十万个请求,反而会造成大量任务堆积。因此优化目标应是提高等待资源的利用率,并让并发量受关键依赖的真实能力约束。
二、虚拟线程的落地方式
在 Java 21 及更高版本中,可以使用
Executors.newVirtualThreadPerTaskExecutor()
为每个任务创建虚拟线程,也可以通过 Spring Boot 等框架启用虚拟线程支持。建议将虚拟线程用于短生命周期的请求任务,而不要把它们当作传统线程池来维护。虚拟线程的优势在于“任务与线程一一对应”,不需要预先建立一个巨大的工作线程池。
需要特别检查同步代码块、JNI 调用、文件系统操作以及某些第三方库。若虚拟线程在 synchronized 代码区域内执行阻塞操作,可能出现 pinning,即虚拟线程无法从载体线程卸载,最终影响调度效率。排查时可使用 JFR 观察虚拟线程相关事件,也可以在测试环境开启较详细的并发诊断日志。对于长时间持有锁的代码,应缩小临界区;对于高竞争锁,应考虑并发容器、分段结构或异步化设计。
虚拟线程适合 I/O 密集型负载,不适合直接解决 CPU 密集型任务。例如图片压缩、复杂 JSON 计算、加密、机器学习推理等操作,最终仍会占满 CPU。此时应限制计算任务并发度,使用固定大小的平台线程池或专门的计算执行器,避免大量虚拟线程同时争抢处理器。调度器的并行度可以根据 CPU 核数和业务特征调整,但通常不应脱离实际压测结果盲目增大。
三、应用部署与资源配置
生产部署首先要确认 JDK 版本、框架版本和容器运行时是否真正支持虚拟线程。容器环境中应关注 cgroup 对 CPU、内存的限制,JVM 感知到的可用处理器数量可能与宿主机总核数不同。若容器只分配了两核,却按照三十二核配置计算线程和连接池,很容易出现上下文切换、CPU 争抢和延迟抖动。
内存配置不能只看
-Xmx
。Java 进程内存包括 Java 堆、元空间、代码缓存、线程栈、直接内存、类库本地内存以及 JNI 分配。虚拟线程虽然比平台线程占用更少的线程栈资源,但大量虚拟线程仍会产生对象、栈帧和上下文数据。建议为容器预留堆外空间,并结合
-Xms
、
-Xmx
、
-XX:MaxMetaspaceSize
、直接内存上限和容器 memory limit 进行整体规划,而不是只把最大堆设置到容器内存的九成。
连接池必须与下游能力匹配。HTTP 客户端连接池、数据库连接池、Redis 连接数、消息消费者并发度都应设置上限,并配合连接超时、读取超时、请求总超时和熔断机制。没有超时的虚拟线程会把故障放大:下游一旦变慢,大量任务会长期驻留,最终消耗内存并形成级联故障。
四、JVM 内存与废品回收监控
排查内存问题时,应区分“堆使用率高”“堆增长过快”“本地内存耗尽”和“容器被杀死”四种情况。可以通过 JMX、Micrometer、Prometheus、Grafana 采集堆区、老年代、元空间、直接内存、线程数量、类加载数量和 GC 暂停时间。若堆在每次 Full GC 后仍持续上升,可能存在缓存无界增长、ThreadLocal 泄漏、监听器未注销或对象被长期引用等问题。
GC 选择不应脱离业务目标。低延迟服务通常关注暂停时间、分配速率和晋升速率,而批处理服务可能更关注吞吐量。G1 适合许多通用服务,但并不意味着调整参数越多越好。应先通过 GC 日志确认问题,再决定是否调整堆大小、年轻代比例、暂停目标或对象分配方式。生产环境建议统一开启 GC 日志滚动,并保留足够时间,便于将故障时段与接口延迟对应起来。
对于堆外问题,可使用 Native Memory Tracking 辅助分析,例如关注线程、类、代码缓存和内部内存类别的变化。若进程 RSS 持续接近容器上限,而堆曲线稳定,就要重点检查直接内存、压缩库、Netty 缓冲区、JDBC 驱动以及本地扩展,而不是继续增加
-Xmx
。
五、性能排查应从现象到证据
发生高延迟时,先看接口分位数而非平均值,重点观察 P95、P99、错误率和吞吐变化。随后建立请求链路:入口线程是否排队,虚拟线程是否大量等待,数据库连接是否耗尽,远程服务是否超时,锁竞争是否升高,GC 是否出现异常暂停。线程转储适合发现死锁、阻塞和大量相同堆栈;JFR 适合从 CPU、锁、分配、文件、网络和线程调度等维度还原时间线;火焰图则适合定位 CPU 热点。
压测时不要只测试理想接口,应分别模拟正常响应、慢查询、连接失败、下游超时和突发流量。记录 CPU、内存、GC、连接池等待时间、线程数、网络带宽及数据库负载,并逐步增加并发,寻找延迟开始陡增的拐点。这个拐点比“理论最大线程数”更有价值,它能帮助确定限流阈值、扩容策略和容量预警线。
六、可执行的优化原则
第一,使用虚拟线程承载大量阻塞型短任务,但限制下游资源并设置超时。第二,将 CPU 密集型工作与 I/O 工作分离,避免共享同一个执行器。第三,减少无界队列、无界缓存和无期限等待。第四,把监控指标、JFR、GC 日志和线程转储纳入日常运维,而不是故障后临时收集。第五,任何 JVM 参数调整都应通过基准测试和灰度发布验证。最终目标不是让线程数量最大,而是在可控内存、稳定延迟和合理资源成本之间取得平衡。









