当前位置:首页 > 文章列表 > 文章 > java教程 > Java JFR 怎样记录自定义事件

Java JFR 怎样记录自定义事件

来源:17golang原创 2026-10-26 03:58:45 0浏览 收藏

如果一次订单处理偶尔变慢,普通日志通常只能告诉我们“结果失败了”或“耗时很长”,却不能把耗时、订单号和商品数量放进同一条 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,却在录制文件里找不到事件”的误判。

自定义事件应服务于一个诊断问题,例如“处理耗时是否与商品数量有关”。字段要围绕这个问题设计:订单标识用于关联业务,商品数量用于解释输入规模,持续时间用于判断慢点。不要把完整请求体、口令或大段异常文本默认塞进事件。

Java JFR 自定义 Event 类型、业务字段与 commit 记录入口的静态结构说明图
图1:自定义 Event 的静态结构说明图,查看事件类型、字段与记录入口的边界;不是运行截图。

继承 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"));
}
Java JFR Recording、enabled、shouldCommit、commit 与 JFR 文件的静态依赖结构说明图
图2:Recording 与事件提交边界说明图,查看采集控制和数据落盘的关系;不是运行结果截图。

如果 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 事件才不会退化成另一种难以维护的字符串日志。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Java Optional 为什么不适合字段类型Java Optional 为什么不适合字段类型
上一篇
Java Optional 为什么不适合字段类型
Java CRC32C 怎样计算分块数据的校验值
下一篇
Java CRC32C 怎样计算分块数据的校验值
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    695次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    707次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    776次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    736次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    569次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码