登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Linux Java 线程 CPU 耗时怎么定位:JFR CPUTimeSample、throttle 与丢样复查

来源:17golang原创

时间:2026-08-21 07:52:03 142浏览 收藏

线上Java服务的接口耗时突然上升时,单看墙上时钟时间很容易误判:一个方法可能只是长时间等网络返回,另一个方法虽然单次请求很快,却在序列化、压缩或者字符串处理上吃掉了大量CPU。Java 25的JFR CPU-Time Profiling在Linux上按CPU实际消耗时间采样线程,刚好能把这两类问题明确区分开。

要点速览
  • Java 25新增的 jdk.CPUTimeSample 是实验性JFR事件,只在Linux平台提供CPU时间采样能力。
  • jcmd 采集运行中的JVM进程,用 jfr view cpu-time-hot-methods 就能完成第一轮文本层面的热点定位。
  • throttle=20ms 更容易捕捉到短时运行的热点方法,但采样越密集,带来的性能开销和生成的数据体量也会越高。
  • 排查的时候要同时对照CPU热点和实际请求耗时,不能直接把CPU消耗时间等同于端到端接口延迟。

先把“慢”拆成CPU忙还是在等待

假设你维护的服务里有两个方法:tenFastRequests() 连续处理十次小请求,oneSlowRequest() 只处理一次逻辑但中间等待100ms。两者的总墙上耗时看起来差不多,但CPU实际消耗完全不一样。前者高并发下很容易成为CPU瓶颈,后者大概率只是在等socket返回数据。

传统的JFR执行采样主要按固定的时间间隔取样,线程处在等待状态的时候基本不会生成执行样本。CPU-time profiling依托Linux的CPU timer机制,只统计线程真正占用CPU运行的时间,最后把结果写入 jdk.CPUTimeSample 事件里。它对native代码的调用栈归因,也比普通的Java栈采样效果更好,不过这个能力目前本身还是实验性的。

Java 25 JFR 将等待型慢请求与 CPU 密集型请求按 CPU 时间区分的前后对比

先确认Linux与JDK 25的采集条件

JEP 509的实现范围只覆盖Linux。开始排查之前先确认待排查的进程和你用来分析的工具来自同一个JDK大版本:

uname -s
java -version
jcmd -l
jfr version

如果服务跑在macOS或者Windows上,这个新事件没法按同样逻辑生效,你可以继续用普通的JFR执行采样,别把平台特性差异当成采集失败。jcmd -l 拿到目标PID之后,再启动短时录制操作就好。

用jcmd录一段可复查的CPU样本

没必要上来就改JVM全局启动配置,先给正在运行的JVM建一段短时间的临时录制。下面的配置把 jdk.CPUTimeSample 打开,设置为每个平台线程每消耗20ms CPU时间就采集一个样本:

jfr configure \
  --input profile.jfc \
  --output /tmp/cpu-profile.jfc \
  jdk.CPUTimeSample#enabled=true \
  jdk.CPUTimeSample#throttle=20ms

jcmd  JFR.start settings=/tmp/cpu-profile.jfc \
  name=cpu-time-check duration=4m filename=/tmp/cpu-profile.jfr

需要手动终止录制的时候执行 jcmd JFR.stop name=cpu-time-check。先采几分钟的可复现流量窗口,一般比直接开长时间连续采样更容易控制开销和生成的文件大小。

Java JFR CPUTimeSample 从 jcmd 录制到 cpu-time-hot-methods 检查的采集链路

先看热点方法,再回到业务调用链

录制完成后,直接用JFR内置的筛选视图找自身CPU占用高的方法:

jfr view cpu-time-hot-methods /tmp/cpu-profile.jfr
jfr view --verbose cpu-time-hot-methods /tmp/cpu-profile.jfr

第一条命令适合快速做初步筛选。如果返回结果里JSON序列化、压缩、正则匹配或者某个批处理循环排在最前面,再切到JMC里查看完整调用树或者火焰图,确认这个热点是业务方法自身的逻辑耗时,还是被下游调用带出来的附加消耗。

别只盯着方法的占比数字看。这个事件里还包含 eventThreadstacktracesamplingPeriodbiased 等字段;如果返回的调用栈是空的,大概率是采样过程中没能完成栈遍历,不要直接判定成“这个线程没有实际工作”。

throttle怎么选:准确度、开销与丢样一起看

设置适合场景主要代价
10ms短时问题复现、热点范围很窄的场景采样更密集,带来的开销和生成的数据文件更大
20ms单次故障排查窗口的通用平衡配置运行时间极短的方法还是有可能被漏掉
500/s按所有平台线程统一控制总采样速率线程数量多之后,单线程的采样分辨率会被自动分摊降低

JEP 509给出的默认throttle配置是 500/s,JDK自带的 profile.jfc 使用的是 10ms。实际排查的时候建议先用20ms的阈值建立基准,再根据热点结果是否稳定调整参数,不要靠一次短时间的录制就断言某个方法绝对占用了多少比例的CPU。

排查过程中还要同步检查丢样事件:

jfr print --events jdk.CPUTimeSamplesLost /tmp/cpu-profile.jfr

jdk.CPUTimeSamplesLost 里的 lostSamples 会提示内部数据结构打满这类实现层面的约束。如果丢样现象很明显,先把采样频率调低或者把录制窗口缩短,再对比两次的采样结果;不然图表看起来数值很精准,最后得出的结论反而偏差很大。

把CPU结论和延迟结论分开验收

CPU profile反映的是相对CPU周期消耗,不等于请求的总耗时。网络读取、锁等待和线程挂起都可能让一个方法的墙上时钟耗时很长,实际消耗的CPU却很少。最终做优化验证的时候,要同时核对接口延迟、CPU使用率、热点方法占比和服务错误率几个指标。

如果热点集中在压缩或者序列化逻辑,优化数据结构、批量大小和底层算法通常比盲目扩线程效果好很多;如果热点占比很稳定但业务吞吐完全没提升,就要进一步确认是不是被数据库、网络或者锁逻辑限制了。不用急着把采样频率直接拉到最高,先保证每一次录制都覆盖的是同一类流量场景。

常见问题

JFR CPU-Time Profiling在macOS上能用吗?

JEP 509的CPU-time profiling实现是面向Linux平台做的。其他平台可以使用JFR自带的普通执行采样,但不能把这个新事件的特性当成跨平台通用的保证。

jdk.CPUTimeSample默认会采集吗?

不会。该事件默认没有启用,需要在JVM启动参数或者JFC配置文件里显式打开才能生成对应的采样数据。

throttle越小是不是一定越准确?

更密集的采样通常能提升短时间窗口内的采样分辨率,但也会增加采样本身的开销和生成的数据量,还可能引发丢样。要以同一流量下多次重复得到的一致结果作为判断依据。

为什么CPU热点最高的方法不是最慢接口?

因为接口总耗时包含大量等待时间,而CPU profile只统计线程实际消耗的CPU cycles。需要把JFR热点数据和接口延迟、全链路调用链放到一起对照分析,才能得到完整结论。

收尾检查清单

一次合格的Java 25 CPU耗时排查,至少要留下Linux系统与JDK版本信息、JFR具体配置、录制时长、throttle参数、热点视图截图和丢样检查结果。只有在相同流量窗口下多次重复得到相近的热点分布,并且优化上线后CPU占用、接口延迟和错误率同步改善,再把对应的优化结论合入生产环境。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>