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

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自定义可配置过滤设置未经实现的任意字段表达式
Recording配置与事件字段的不同职责
事件字段与筛选条件的静态分层说明图。配置组控制类型和时长,字段组只保存业务值,二者不是同一种筛选机制。

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 中单独捕获演示异常,是为了继续导出文件。

failed或rows条件与shouldCommit共同约束事件提交
业务谓词与提交判断的静态依赖说明图。业务谓词与记录判断共同限制详情准备和提交,图中的连线表示依赖关系。

若目标改成“失败或行数多,且耗时不少于 20 毫秒”,可把启用配置改为 withThreshold(java.time.Duration.ofMillis(20))。这时快速失败也可能被时长门槛排除。若需求是“所有失败都保留,成功操作只保留慢请求”,可以拆成失败事件与耗时事件分别配置,避免把一个统一阈值误当成带例外的业务规则。

代码中的 rows 由调用方传入,代表已知任务规模。若要按实际成功行数过滤,应在业务处理过程中得到真实计数,再填入事件;不能把计划行数当成完成行数。示例提前保存 enabled,适用于录制在动作前已经启动的场景;运行中动态开启时,不应期待它回补已经开始但未计时的调用。

反向推导:用条件表解释预期,而不是只看文件是否生成

操作rowsfailed无时长门槛时的业务判断
lookup0false不提交
bulk8false满足条件
failed0true满足条件

以上是代码的预期条件表,没有声称本机运行记录。文件能够导出只说明有可导出的录制数据,不代表每条业务条件都正确。排查时应把启用类型、运行区间、计时边界和业务谓词分开看,不能把“文件存在”当成筛选逻辑的结论。

同一 JVM 可能同时存在多个 Recording。isEnabled 关注是否至少有一个运行记录启用该事件;shouldCommit 使用运行记录的有效阈值判断,其中时长门槛取各运行记录的最小值。因此不能把生产端的判断解释成“只代表我手里的这个 Recording”。配置冲突时,要先确认是否有其他诊断记录正在运行。

检查清单:什么时候需要可配置字段过滤

固定业务规则优先使用本文的 if 判断。若希望不改埋点代码,通过记录配置调整字段筛选,才考虑自定义 SettingControl:在 Event 上定义带 @SettingDefinition 的过滤方法,接收具体 SettingControl 子类并返回 boolean,由控制类解释设置值,再通过 EventSettings.with 传入自定义设置。配置键必须对应实际实现,不能随意写一个 rows>=5 就期待 JFR 执行。

自定义控制类需要处理默认值和非法输入;多个记录同时运行时,combine 应合并各自需求,使每个记录至少获得请求的数据。比如“最小行数”过滤通常需要采用更宽松的最小阈值,而不是最大值。过滤方法也应保持轻量,不把数据库查询或远程调用放在判断里。本文不展开完整控制类,以免把固定条件示例变成配置框架。

相关问题

在查看工具里筛选字段算不算减少记录量

不算生产端裁剪。消费端筛选能减少显示或后续处理的数据,但原始事件可能已经写入记录。需要减少采集量,应在提交前应用规则或配置合适的事件设置。

没有调用 end,commit 会不会自动结束事件

从 begin 开始且没有显式 end 时,commit 会结束计时。但本文在 shouldCommit 前显式 end,是为了先固定时长,再决定是否准备详情。

业务失败是否应该吞掉异常方便导出

不应该。记录属于观测职责,业务异常继续按调用契约传播;需要导出录制文件时,可由外层管理录制的代码处理保存与资源关闭。

参考资料

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