为何JVM能在仅10物理线程的机器上运行数百线程?
问题梳理
一、核心矛盾与测试背景
机器仅有10个物理线程,JVM线程映射到OS线程,但无法理解JVM为何能运行数百线程。原本以为是时间分片机制,但实际测试结果不符。
搭建了一个Spring Boot应用,接口会调用存在3秒延迟的外部服务,分别设置server.tomcat.threads.max=10和使用默认值(200)进行ab测试,结果差异悬殊:
测试1:设置server.tomcat.threads.max=10的ab结果
>ab -n 1000 -c 100 http://localhost:8080/block This is ApacheBench, Version 2.3 <$Revision: 1913912 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: Server Hostname: localhost Server Port: 8080 Document Path: /block Document Length: 39 bytes Concurrency Level: 100 Time taken for tests: 304.367 seconds Complete requests: 1000 Failed requests: 100 (Connect: 0, Receive: 0, Length: 100, Exceptions: 0) Total transferred: 172100 bytes HTML transferred: 39100 bytes Requests per second: 3.29 [#/sec] (mean) Time per request: 30436.732 [ms] (mean) Time per request: 304.367 [ms] (mean, across all concurrent requests) Transfer rate: 0.55 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.7 0 7 Processing: 3031 28738 4956.5 30102 30238 Waiting: 3028 28738 4956.8 30101 30238 Total: 3034 28739 4955.9 30102 30239 Percentage of the requests served within a certain time (ms) 50% 30102 66% 30112 75% 30120 80% 30134 90% 30174 95% 30205 98% 30223 99% 30230 100% 30239 (longest request)
测试2:默认server.tomcat.threads.max=200的ab结果
ab -n 1000 -c 100 http://localhost:8080/block This is ApacheBench, Version 2.3 <$Revision: 1913912 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: Server Hostname: localhost Server Port: 8080 Document Path: /block Document Length: 39 bytes Concurrency Level: 100 Time taken for tests: 33.435 seconds Complete requests: 1000 Failed requests: 909 (Connect: 0, Receive: 0, Length: 909, Exceptions: 0) Total transferred: 173415 bytes HTML transferred: 40415 bytes Requests per second: 29.91 [#/sec] (mean) Time per request: 3343.475 [ms] (mean) Time per request: 33.435 [ms] (mean, across all concurrent requests) Transfer rate: 5.07 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 1.0 0 11 Processing: 3002 3023 15.7 3021 3172 Waiting: 3002 3023 15.6 3020 3168 Total: 3002 3024 16.3 3021 3172 Percentage of the requests served within a certain time (ms) 50% 3021 66% 3026 75% 3029 80% 3031 90% 3041 95% 3063 98% 3075 99% 3078 100% 3172 (longest request)
二、额外疑问
- 已通过日志确认应用创建了数百线程,但仍不解:为何200线程在仅10物理线程的机器上运行速度明显更快?
- 如果JVM线程是平台线程,那Java虚拟线程的作用是什么?开启虚拟线程测试后,结果与平台线程一致。
解答
1. 200线程比10线程快的核心原因
你的接口属于IO密集型任务:每个请求有3秒的外部服务调用延迟,线程在这段时间完全不占用CPU,处于阻塞等待状态。
- 当Tomcat线程池设为10时,同一时间最多只能处理10个请求,剩下90个请求进入队列排队。1000个请求需要分100批处理,每批耗时约3秒,总耗时约300秒,与测试结果吻合。
- 当线程池设为200时,100个并发请求可同时被处理,每个请求仅需3秒左右完成,总耗时直接降到33秒。
OS线程调度机制在此场景的作用是:当线程进入IO阻塞时,OS会立刻将对应的物理线程调度给其他需要执行的线程。因此即使有200个OS线程,只要大部分处于等待状态,10个物理线程完全能支撑,且不会产生严重的线程切换开销——因为等待的线程根本不需要CPU资源。
2. Java虚拟线程的核心价值
普通Java线程就是平台线程,与OS线程一一绑定。虚拟线程是JVM实现的轻量级线程,由JVM自主调度,无需一直占用OS线程:
- 当虚拟线程遇到IO阻塞时,JVM会释放它绑定的OS线程,让其他虚拟线程复用该OS线程,避免了OS层面的线程切换开销(OS线程切换的开销远大于JVM内部调度)。
- 虚拟线程内存占用极低(栈内存初始仅几KB),可轻松创建数万甚至数十万实例;而平台线程每个栈内存达MB级,创建数百个就会占用大量内存,并发量提升后OS线程切换开销会急剧增加。
你测试时并发量仅为100,平台线程池200已完全能应对,因此虚拟线程的优势未体现。若将并发量提升至数千甚至数万,平台线程池可能因内存不足、切换开销过大而性能暴跌,此时虚拟线程的高并发优势才会显现。
内容的提问来源于stack exchange,提问作者PROvishesh
相关产品推荐
相关产品推荐

