Java JFR 如何只录制一次慢请求窗口
来源:17golang原创
时间:2026-09-07 18:31:19 395浏览 收藏
Java 服务出现偶发慢请求时,不必先把 JFR 常驻成一份巨大的连续录制。更稳妥的做法是:从 default.jfc 复制一份临时配置,给关心的阻塞事件设置阈值,再用 jcmd JFR.start 只录制 30 秒,最后用 jfr summary 和 jfr print 读取同一个 .jfr 文件。这样得到的是一次有边界的现场,既能保留调用栈,又不会把所有短事件都混进结果。
duration=30s决定这次录制何时自动结束,filename决定结果落到哪个文件。threshold只过滤事件,不代表整个 HTTP 请求耗时;应用路由需要自定义 JFR 事件或结合其他证据。- 先看事件总量,再看
jdk.SocketRead、jdk.JavaMonitorWait和调用栈,才能判断慢点落在哪个边界。
先把一次录制的边界定下来
这类排查至少有四个边界:业务请求事件、事件阈值、录制窗口和结果文件。JFR 的 duration 事件只有达到 threshold 才会被记录;窗口结束后,数据写入一个可复查的 slow-window.jfr。如果只观察网络或锁,可以直接使用 JFR 内置事件;如果必须按路由定位,就在服务代码里增加一个自定义请求事件,让它的字段包含路由、状态码和耗时。

从 default.jfc 生成只保留慢事件的配置
不要直接修改 JDK 自带的配置文件。用 jfr configure 从默认配置生成一份项目专用文件,只调整本次要看的 duration 事件。例如下面把网络读写和锁等待都设为 20 毫秒;短于这个值的事件不会进入这次录制。
# 从默认配置派生临时配置,不改动 JDK 自带文件
jfr configure --input "$JAVA_HOME/lib/jfr/default.jfc" \
--output /opt/jfr/slow-request.jfc \
jdk.SocketRead#threshold=20ms \
jdk.SocketWrite#threshold=20ms \
jdk.JavaMonitorWait#threshold=20ms
如果运行环境的 JDK 没有这个路径,先用 jfr configure --help 和实际的 JAVA_HOME 确认配置位置。阈值越低,捕获的短事件越多;阈值越高,文件更干净,但可能漏掉由大量短等待叠加形成的请求问题。
用 jcmd 启动一次性慢请求窗口
找到目标 JVM 的 PID 后,显式指定名称、配置、时长和文件名。duration=30s 到期后自动结束,适合让问题复现一次;不要省略 duration,否则默认值可能让录制一直运行。
# 先确认目标进程,再启动唯一一个 30 秒录制
jcmd VM.version
jcmd JFR.start name=slow-window \
settings=/opt/jfr/slow-request.jfc \
duration=30s \
filename=/tmp/slow-window.jfr
# 复现慢请求期间查看录制状态
jcmd JFR.check name=slow-window
需要按路由和状态码判断时,可以在请求入口包住一个应用事件。事件类上的 @Threshold 是默认过滤线,commit() 会结束本次事件的计时;下面的字段会随事件写入 JFR。
import jdk.jfr.Category;
import jdk.jfr.Event;
import jdk.jfr.Label;
import jdk.jfr.Name;
import jdk.jfr.Threshold;
@Name("demo.HttpRequest")
@Label("HTTP Request")
@Category({"Demo", "HTTP"})
@Threshold("200 ms")
final class HttpRequestEvent extends Event {
@Label("Route") String route;
@Label("Status") int status;
}
// 在请求入口开始,在响应状态确定后提交事件
HttpRequestEvent event = new HttpRequestEvent();
event.route = "/orders/{id}";
event.begin();
try {
// 这里调用实际业务处理,并在结束前写入状态码
event.status = 200;
} finally {
event.commit();
}
这段自定义事件只说明“哪个请求超过 200 毫秒”,不替代网络、锁和 CPU 事件。真实项目中应把 route 做成低基数模板,避免把用户参数直接写进事件字段。
用 jfr summary 与 print 读取同一个文件
录制结束后先做摘要,再做定向读取。摘要用来确认文件确实包含目标事件,定向输出用来抓 duration、线程和调用栈。
# 先看事件数量和录制基本信息
jfr summary /tmp/slow-window.jfr
# 再读取可能造成慢请求的阻塞事件与调用栈
jfr print --events jdk.SocketRead,jdk.SocketWrite,jdk.JavaMonitorWait \
--stack-depth 20 /tmp/slow-window.jfr
# 如果写入了应用级事件,再单独过滤它
jfr print --events demo.HttpRequest /tmp/slow-window.jfr

判断时不要把一个 jdk.SocketRead 直接当成接口总耗时:它说明线程在 socket 读取上等待过;jdk.JavaMonitorWait 更接近锁或条件等待;自定义 demo.HttpRequest 才能给出路由级的请求窗口。三者的线程、时间范围和调用栈能够相互印证时,结论才足够可靠。
常见问题
为什么设置了 threshold 仍然看不到某个慢请求?
threshold 只作用于对应事件类型,且事件必须真的被启用。路由级耗时如果没有自定义事件,单看 SocketRead 不能覆盖全部业务执行时间。
duration 到期后还需要执行 JFR.stop 吗?
不需要。duration 到期会结束这次录制并写入 filename;只有提前结束或需要立即落盘时,才用带 name 的 JFR.stop。
为什么不直接使用 profile?
profile 适合短时间获取更多信息,但仍可能产生更多事件。先从 default 配置派生并提高目标事件阈值,更适合一次只看慢请求窗口的排查。
最终检查只看三件事:录制是否只有一个明确的 name,文件是否按预期生成,以及结果中是否同时出现事件类型、duration 和可解释的调用栈。满足这三点,就可以把 JFR 现场交给后续的代码或链路排查。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
文章 · java教程 | 2小时前 | 并发编程 · Java教程 · ConcurrentHashMap · java concurrenthashmap 并发map computeIfAbsent 递归更新323 收藏
-
175 收藏
-
445 收藏
-
129 收藏
-
367 收藏
-
文章 · java教程 | 8小时前 | Java · httpclient · BodySubscriber · 响应体大小 · java httpclient BodyHandler BodyHandlers.limiting263 收藏
-
157 收藏
-
373 收藏
-
366 收藏
-
415 收藏
-
310 收藏
-
351 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习