开放问答
JDK21 虚拟线程高并发下 CPU 只有六成:怎么系统性拆出 carrier 调度延迟占比
该问题的正文、回答与评论。
提问者 林星河-781 · 状态 open
虚拟线程JDK21高并发profiling性能调优
【背景】
我负责一个对外查询聚合服务:Spring Boot 3.2 + JDK 21.0.3(spring.threads.virtual.enabled=true)+ HikariCP 5.1 + MySQL 8.0,单机 8C16G,K8s CPU limit=4。入口 QPS 稳态 400,峰值 1200,单请求内并行 fan-out 3 个下游(HTTP,OkHttp 4.12)后再做一次聚合查询。稳态并发量级 5k~15k 虚拟线程。
【现象】 P50 一直 35ms 没问题,但 P99 间歇性从 120ms 飙到 480ms,尖峰能到 900ms。监控看 CPU 只到 55%~65%(不是 CPU 瓶颈),GC 几乎无(ZGC,pause < 2ms),下游三个服务的 P99 都在 80ms 以内,MySQL 慢日志干净。也就是说:CPU 不饱和、下游不慢、GC 不抖,但本服务 P99 却抖。
【已尝试的排查与结果】
async-profiler -e cpu采 60s:火焰图里业务代码占比很小,看不出热点。async-profiler -e wall -t采 60s:火焰图被Unsafe.park/ForkJoinPool.managedBlock/VirtualThread.park占满,但下面没有可辨识的业务栈,无法区分是「等 carrier 调度」还是「等真实 IO / 锁」。- JFR 开 60s(
jfr.profile):rec 文件 1.2GB,Threads 视图 1w+ 虚拟线程,JDK Mission Control 打开即卡死,没法看。 jcmd <pid> Thread.print抓了几次:能看到java.lang.VirtualThread状态分布(RUNNABLE/PARKED),但没有数值化的「park 等待时长」分布。- HikariCP metrics 拉了:
pending平时 0,尖峰时 0~3,基本排除连接池等。
【卡点】 核心问题:CPU 不饱和 + 下游不慢的情况下,P99 抖动到底是 (a) carrier 线程被 pin / 抢占导致虚拟线程排队、(b) 虚拟线程在 synchronized 块里被钉住、(c) 某条隐藏路径里 park 等锁、还是 (d) GC / 系统调用? 我需要一个可复现的、按步骤的排查方法论,而不只是「用 async-profiler 看火焰图」这种我已经做过但没拿到结论的建议。
【期望得到的答案形态】
- 一套具体的 profiling 命令序列(async-profiler / JFR 的参数、采样时长、如何切分 carrier vs virtual thread 的栈);
- 一个可量化的指标或公式:怎么从 profiling 数据里算出「carrier 调度延迟占 P99 抖动的比例」;
- 针对 (a)/(b)/(c)/(d) 四种假设各自的判定标志(哪个栈帧 / 哪个 JFR event 出现即可以排除或确认);
- 如果有过类似场景(CPU 50%~70% + 虚拟线程 + P99 抖)的实战案例,期望能贴出你当时的关键数据点和最终定位。
已采纳
回答者
回答正文
评论
暂无评论。