Java JFR Event 怎样只记录满足条件的事件
来源:17golang原创
时间:2026-11-14 05:52:24 412浏览 收藏
Java 自定义 JFR Event 要只记录满足条件的事件,最直接的做法是在生产端用业务条件决定是否调用 commit(),再用 shouldCommit() 避免为未启用或未达到时长阈值的事件准备昂贵字段。事件类型开关、事件持续时长和字段业务条件是三个不同层次,给字段赋值不会自动建立过滤规则。
官方地址:https://docs.oracle.com/en/java/javase/25/
现象:已经设置字段,为什么普通事件仍然出现
假设批处理诊断只关心两类操作:执行失败,或者处理行数至少为 5。为 Event 增加 failed 与 rows 字段后,如果所有调用都执行 commit,普通操作依旧会进入记录。字段负责描述发生了什么,是否保留事件则需要额外条件。
另一个常见混淆是把 threshold 当成任意数值字段的阈值。JFR 的内置时长阈值针对事件持续时间,不会自动理解“rows 大于 5”或者“业务状态异常”。先区分这几层,就不必在事件找不到时反复修改字段名称。
分层检查:先看类型开关,再看时长与业务谓词
| 检查层 | 控制对象 | 不能替代的条件 |
|---|---|---|
| Recording.enable/disable | 某个事件类型的启停 | 每条事件的业务字段判断 |
| withThreshold | 持续时间门槛 | 处理行数或错误类别 |
| 生产端 if 判断 | 业务谓词 | 正在运行的记录配置 |
| SettingControl | 自定义可配置过滤设置 | 未经实现的任意字段表达式 |

isEnabled() 适合判断当前是否有启用该事件的运行记录,不能据此保证事件一定写入。对于持续时间条件,应在实际业务前 begin,结束后 end,然后判断 shouldCommit。若在开始计时前就拿 shouldCommit 判断一个慢操作是否值得记录,还没有获得完整时长,判断位置就错了。
证据判断:shouldCommit 不会读取你心里的过滤表达式
shouldCommit() 提供启用状态与时长方面的提交判断。没有自己实现业务谓词时,它不会自动把 failed 当成“只保留失败”的配置。最小方案因此把业务条件直接写成 failed || rows >= 5,让判断意图在代码中可见。
当字段采集比较昂贵时,先填充用于业务判断的低成本字段;只有业务条件和提交判断都成立,才准备额外详情。记录中的字段应采用 JFR 支持的类型。本文使用 String、int 和 boolean,不把任意业务对象直接塞进事件;例如一个 List 字段并不会自动序列化成完整业务列表。
修复动作:异常或行数达到条件才提交
下面是可保存为 ConditionalJfr.java 的独立示例,使用常规 JFR API。演示动作是空任务与主动抛出的异常,仅用于说明条件,不是性能基准。示例不设置时长门槛,因此方便单独理解业务筛选。
import jdk.jfr.*;
import java.nio.file.Path;
public class ConditionalJfr {
@Name("demo.BatchOperation")
@Label("批处理操作")
@Enabled(false)
static class BatchEvent extends Event {
@Label("操作") String operation;
@Label("行数") int rows;
@Label("失败") boolean failed;
@Label("详情") String detail;
}
static void observe(String operation, int rows, Runnable action) {
BatchEvent event = new BatchEvent();
boolean enabled = event.isEnabled();
boolean failed = false;
if (enabled) event.begin();
try {
// 即使没有开启记录,也必须执行真实业务。
action.run();
} catch (RuntimeException | Error problem) {
failed = true;
throw problem;
} finally {
if (enabled) {
// 先结束计时,再检查固定后的事件时长。
event.end();
event.operation = operation;
event.rows = rows;
event.failed = failed;
boolean interesting = failed || rows >= 5;
if (interesting && event.shouldCommit()) {
// 仅在值得提交时组装额外详情。
event.detail = "operation=" + operation + ", rows=" + rows;
event.commit();
}
}
}
}
public static void main(String[] args) throws Exception {
try (Recording recording = new Recording()) {
// 本例去掉时长阈值,只观察业务条件是否成立。
recording.enable(BatchEvent.class).withoutThreshold();
recording.start();
observe("lookup", 0, () -> {});
observe("bulk", 8, () -> {});
try {
observe("failed", 0, () -> {
throw new IllegalStateException("demo failure");
});
} catch (IllegalStateException expected) {
// 示例继续导出;观测代码没有吞掉业务异常。
}
recording.stop();
recording.dump(Path.of("conditional.jfr"));
} // 释放 recording 资源
}
}
类上 @Enabled(false) 表达默认关闭意图,本次 Recording 再显式启用这个类型。没有运行记录时,action 仍然执行,事件开关不会变成业务开关。捕获到的异常继续向上传递,finally 只负责完成观测;main 中单独捕获演示异常,是为了继续导出文件。

若目标改成“失败或行数多,且耗时不少于 20 毫秒”,可把启用配置改为 withThreshold(java.time.Duration.ofMillis(20))。这时快速失败也可能被时长门槛排除。若需求是“所有失败都保留,成功操作只保留慢请求”,可以拆成失败事件与耗时事件分别配置,避免把一个统一阈值误当成带例外的业务规则。
代码中的 rows 由调用方传入,代表已知任务规模。若要按实际成功行数过滤,应在业务处理过程中得到真实计数,再填入事件;不能把计划行数当成完成行数。示例提前保存 enabled,适用于录制在动作前已经启动的场景;运行中动态开启时,不应期待它回补已经开始但未计时的调用。
反向推导:用条件表解释预期,而不是只看文件是否生成
| 操作 | rows | failed | 无时长门槛时的业务判断 |
|---|---|---|---|
| lookup | 0 | false | 不提交 |
| bulk | 8 | false | 满足条件 |
| failed | 0 | true | 满足条件 |
以上是代码的预期条件表,没有声称本机运行记录。文件能够导出只说明有可导出的录制数据,不代表每条业务条件都正确。排查时应把启用类型、运行区间、计时边界和业务谓词分开看,不能把“文件存在”当成筛选逻辑的结论。
同一 JVM 可能同时存在多个 Recording。isEnabled 关注是否至少有一个运行记录启用该事件;shouldCommit 使用运行记录的有效阈值判断,其中时长门槛取各运行记录的最小值。因此不能把生产端的判断解释成“只代表我手里的这个 Recording”。配置冲突时,要先确认是否有其他诊断记录正在运行。
检查清单:什么时候需要可配置字段过滤
固定业务规则优先使用本文的 if 判断。若希望不改埋点代码,通过记录配置调整字段筛选,才考虑自定义 SettingControl:在 Event 上定义带 @SettingDefinition 的过滤方法,接收具体 SettingControl 子类并返回 boolean,由控制类解释设置值,再通过 EventSettings.with 传入自定义设置。配置键必须对应实际实现,不能随意写一个 rows>=5 就期待 JFR 执行。
自定义控制类需要处理默认值和非法输入;多个记录同时运行时,combine 应合并各自需求,使每个记录至少获得请求的数据。比如“最小行数”过滤通常需要采用更宽松的最小阈值,而不是最大值。过滤方法也应保持轻量,不把数据库查询或远程调用放在判断里。本文不展开完整控制类,以免把固定条件示例变成配置框架。
相关问题
在查看工具里筛选字段算不算减少记录量
不算生产端裁剪。消费端筛选能减少显示或后续处理的数据,但原始事件可能已经写入记录。需要减少采集量,应在提交前应用规则或配置合适的事件设置。
没有调用 end,commit 会不会自动结束事件
从 begin 开始且没有显式 end 时,commit 会结束计时。但本文在 shouldCommit 前显式 end,是为了先固定时长,再决定是否准备详情。
业务失败是否应该吞掉异常方便导出
不应该。记录属于观测职责,业务异常继续按调用契约传播;需要导出录制文件时,可由外层管理录制的代码处理保存与资源关闭。
参考资料
-
501 收藏
-
文章 · java教程 | 2个月前 | Java · 异常处理 · 资源管理 · java try-with-resources AutoCloseable close suppressed exception501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
266 收藏
-
335 收藏
-
445 收藏
-
339 收藏
-
146 收藏
-
248 收藏
-
336 收藏
-
233 收藏
-
315 收藏
-
333 收藏
-
文章 · java教程 | 12小时前 | 字符串 · 工程实践 · Java教程 · java Locale.ROOT String.toLowerCase String.toUpperCase 机器可读字符串436 收藏
-
文章 · java教程 | 12小时前 | Java · 文件下载 · java httpclient Content-Disposition BodyHandlers ofFileDownload filename438 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习