Java JFR 怎样记录自定义事件
来源:17golang原创
时间:2026-10-26 03:58:45 456浏览 收藏
如果一次订单处理偶尔变慢,普通日志通常只能告诉我们“结果失败了”或“耗时很长”,却不能把耗时、订单号和商品数量放进同一条 JVM 诊断记录。Java JFR 的自定义事件适合补上这层信息:继承 jdk.jfr.Event 定义事件和字段,再由 Recording 决定什么时候采集。
官方地址:https://docs.oracle.com/en/java/javase/25/docs/api/jdk.jfr/jdk/jfr/Event.html
这篇文章只解决一个边界:怎样定义并记录一个业务事件,不讨论把 JFR 当作日志系统,也不把示意图当成真实运行截图。
先把 Event、事件实例和 Recording 分开
可以把一次慢订单处理拆成三层。OrderLatency 是事件类型,描述有哪些字段;事件实例承载某一次订单处理的数据;Recording 决定这类事件是否进入当前采集范围并负责导出结果。三者混在一起时,最容易出现“代码调用了 commit,却在录制文件里找不到事件”的误判。
自定义事件应服务于一个诊断问题,例如“处理耗时是否与商品数量有关”。字段要围绕这个问题设计:订单标识用于关联业务,商品数量用于解释输入规模,持续时间用于判断慢点。不要把完整请求体、口令或大段异常文本默认塞进事件。

继承 Event,并给字段补上可读元数据
下面的示例定义一个订单延迟事件。@Name 用于稳定的事件标识,@Label 和 @Description 让查看事件的人更容易理解字段。代码展示的是 API 用法,不在本机执行目录或业务任务。
import jdk.jfr.Description;
import jdk.jfr.Event;
import jdk.jfr.Label;
import jdk.jfr.Name;
@Name("com.example.OrderLatency")
@Label("订单处理延迟")
@Description("记录一次订单处理的输入规模与耗时")
final class OrderLatency extends Event {
@Label("订单号")
String orderId;
@Label("商品数量")
int itemCount;
@Label("结果")
String result;
}
事件字段应选择 JFR 支持的简单类型并保持体积可控。事件类是结构定义,不是每次请求都共享的全局状态;真正记录一次调用时再创建一个事件实例,给字段赋值后提交。
用 begin、end 和 commit 表达一次业务耗时
如果要记录持续时间,可以在业务操作前调用 begin(),结束后调用 end(),再调用 commit()。如果事件没有显式调用 end(),在提交时也会结束事件,但把边界写出来更容易让代码审查者看懂。
OrderLatency event = new OrderLatency();
event.orderId = orderId;
event.itemCount = itemCount;
event.begin();
try {
// 这里代表实际业务调用,示例只说明事件生命周期。
processOrder(orderId);
event.result = "success";
} catch (RuntimeException ex) {
// 只保留有限的结果标识,避免把敏感异常全文写进诊断事件。
event.result = "failure";
throw ex;
} finally {
// 无论成功还是失败,都结束计时并提交这次事件。
event.end();
event.commit();
}
这里的 finally 是关键:成功、业务异常和提前退出都共享一个结束点。若事件只是表达瞬时事实,也可以不调用 begin/end,赋值后直接 commit()。
为什么提交前还要看 shouldCommit
事件对象很轻时,直接提交通常够用;如果字段需要格式化、序列化或计算昂贵摘要,可以先用 shouldCommit() 判断当前是否有录制会写入这类事件。它不是业务正确性的判断,也不代表事件一定已经写入文件,只是帮助调用方避免在没有采集需求时准备昂贵数据。
OrderLatency event = new OrderLatency();
event.orderId = orderId;
if (event.shouldCommit()) {
// 只有可能被采集时才计算成本较高的诊断字段。
event.itemCount = countItems(orderId);
event.result = "success";
event.commit();
}
这段写法的前提是业务代码不依赖事件字段去完成真正的业务逻辑。若 itemCount 同时是业务结果的一部分,就应先完成业务计算,再决定是否把它写进事件;不要因为没有 Recording 就跳过业务必需的数据。
Recording 决定采集边界,不是 Event 的替代品
要得到一个可分析的 JFR 文件,需要让 Recording 覆盖业务调用,再停止并导出。示例用 try-with-resources 管理 Recording,确保正常结束和异常路径都能关闭资源:
import java.nio.file.Path;
import jdk.jfr.Recording;
try (Recording recording = new Recording()) {
// 开启当前录制范围;事件类型仍由自定义 Event 类定义。
recording.start();
OrderLatency event = new OrderLatency();
event.orderId = "order-42";
event.itemCount = 3;
event.begin();
try {
processOrder(event.orderId);
event.result = "success";
} finally {
// 让持续时间和提交动作处于同一业务边界。
event.end();
event.commit();
}
recording.stop();
// dump 只导出本次 Recording 的数据,不等于线上页面验证。
recording.dump(Path.of("order-latency.jfr"));
}

如果 Recording 没有覆盖事件提交,或者该事件被设置为 disabled,文件中自然可能没有记录。事件没有出现时,应先检查采集边界、事件设置和提交点,不要先把问题归因于字段注解。
生产落地时保留四个约束
| 约束 | 落地判断 |
|---|---|
| 事件粒度 | 围绕一个诊断问题建模,避免每个普通日志都变成事件 |
| 字段成本 | 限制字符串长度和计算成本,昂贵字段配合 shouldCommit |
| 敏感信息 | 不把口令、完整请求体和个人数据直接写入 JFR |
| 资源边界 | Recording 用 try-with-resources,事件生命周期覆盖成功与异常路径 |
最后可以用一张清单排查:事件类是否继承 Event;字段是否回答诊断问题;持续时间是否在同一个 begin/end 边界内;提交前是否误把 shouldCommit 当成业务条件;Recording 是否覆盖了事件提交并正确停止。
常见问题
自定义 Event 一定要手动注册吗?
不一定。普通事件类可以按 JFR 的默认注册机制使用;只有需要控制注册时机或可见性时,才考虑 FlightRecorder.register 与 @Registered 这一组 API。
调用 commit 后为什么文件里没有事件?
先确认 Recording 正在运行并覆盖提交点,再检查事件是否被禁用以及事件名称是否与查看工具中的类型一致。commit() 表示提交请求,不等于任何时刻都必然产生可见记录。
总结一下:用 Event 描述一次诊断事实,用 begin/end 表示耗时边界,用 commit 写入,用 shouldCommit 控制昂贵准备,用 Recording 管理采集范围。这样自定义 JFR 事件才不会退化成另一种难以维护的字符串日志。
-
479 收藏
-
337 收藏
-
128 收藏
-
149 收藏
-
202 收藏
-
293 收藏
-
189 收藏
-
179 收藏
-
128 收藏
-
315 收藏
-
189 收藏
-
307 收藏
-
161 收藏
-
441 收藏
-
435 收藏
-
280 收藏
-
147 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习