Ohhnews

分类导航

$ cd ..
DZone Java原文

Jeffrey Microscope:Java火焰图生成与分析工具

#jeffrey microscope#jfr#火焰图#性能分析#可视化

Java Flight Recorder(JFR)能够捕获应用程序运行时极其详尽的细节——但原始 JFR 文件的价值完全取决于你用来探索它们的工具。Jeffrey 是一款开源 JFR 分析器,专注于将 JFR 事件转化为交互式可视化图表,而 Jeffrey Microscope 则是其独立单用户部署版本:一个自包含的应用,让你可以在浏览器中直接导入录制数据,深入探索火焰图、时间序列及其他视图。开始使用只需一分钟:

  • 独立 JAR – 从 GitHub Releases 页面下载最新的 microscope.jar,然后用 java -jar microscope.jar 启动(需 Java 25 或更新版本)。
  • Docker – 完全跳过环境配置,直接运行 docker run -it --network host petrbouda/microscope
  • 示例录制文件 – 如果你想在分析自己的应用之前先探索该工具,petrbouda/microscope-examples 镜像预装了示例录制文件(运行 docker run -it --network host petrbouda/microscope-examples)。

在本文中,我们将使用 Jeffrey Microscope 分析 JFR 火焰图,并逐步讲解它们如何帮助你找到应用程序实际花费时间的地方。让我们搭建一个实际动手的环境。从 GitHub Releases 页面 下载最新的 microscope.jar 并启动它(需 Java 25 或更新版本):

Video 10 Audio 5 Audio 6 访问广告商网站 GO TO PAGE

$ bash
java -jar microscope.jar

在浏览器中打开它,然后获取一些录制文件进行分析——Jeffrey 维护了一个配套仓库,包含从各种序列化和性能分析场景中捕获的真实 JFR 录制文件:

$ bash
git clone https://github.com/petrbouda/jeffrey-recordings

文件以压缩的 .jfr.lz4 格式提供,Jeffrey Microscope 原生支持读取。将文件拖放到仪表板上的 Drop Recordings 区域——上传会自动开始,几秒钟后你就可以获得一个可供探索的性能分析文件。

[LOADING...]

在本教程中,我们将重点分析两个录制文件,它们对同一段代码进行了性能分析——该代码是一个用于序列化和反序列化 JSON 的 HTTP 端点——但两者之间有一个有意的区别:

  • jeffrey-persons-direct-serde-cpu.jfr.lz4 – 优化路径。JSON 直接在 Java 对象之间进行序列化和反序列化,并额外使用了缓存。
  • jeffrey-persons-dom-serde-cpu.jfr.lz4 – 未优化路径。JSON 在转换为 Java 对象之前先经过 DOM 表示(JsonNode),从而故意产生额外垃圾。

由于这两个录制文件在相同负载下执行相同的端点,因此它们非常适合作为前后对比,用于生成火焰图和差分图,正如我们稍后将展示的那样。

探索主火焰图

让我们从优化录制文件开始。点击 jeffrey-persons-direct-serde-cpu.jfr.lz4 打开其性能分析文件,然后转到 Visualization 选项卡,在侧边栏的 Flamegraphs 下选择 Primary。Jeffrey 会检查录制文件,并为找到的每个可生成火焰图的事件类型显示一张卡片——每张卡片都可以独立渲染:

  • Execution Samplesjdk.ExecutionSample)– 通过 perf_events 进行的 CPU 性能分析,对于像这样的 CPU 性能分析文件来说,这是最相关的卡片。
  • Wall-Clock Samplesprofiler.WallClockSample)– 墙钟时间,包括等待时间。
  • Allocation Samplesjdk.ObjectAllocationInNewTLAB)– 内存分配,可按对象数量或总字节数加权。
  • Java Monitor BlockedJava Thread ParkJava Monitor Wait – 锁争用和线程暂停事件。

[LOADING...]

每张卡片显示事件类型、来源(Async-Profiler 或 JDK)、样本数量以及一些渲染选项——例如,Use Thread-mode 可按线程拆分图表,Use Total Allocation 可按字节数而非样本数对分配火焰图进行加权。点击 Execution Samples 卡片上的 View Flamegraph 即可查看 CPU 时间消耗在哪里。

时间序列

[LOADING...]

在火焰图上方,Jeffrey 会将所选事件绘制在录制文件的时间线上,因此你可以看到活动随时间的变化——预热、稳态和峰值都一目了然。拖动下方 range selector(范围选择器)上的手柄可以缩小时间窗口,火焰图会仅基于该时间间隔内的样本重新构建。

火焰图

[LOADING...]

每个矩形框都是一个栈帧,其宽度与捕获到该栈帧的样本数量成比例,从下向上堆叠,直至在 CPU 上运行的方法。宽框就是时间消耗的地方。从上到下阅读,可以跟踪从入口点到自身代码的完整调用路径。点击任意帧即可放大到该子树。

搜索

[LOADING...]

搜索框会高亮所有与查询匹配的帧,并报告这些匹配项在整个性能分析结果中所占的比例——这是一种快速回答“我的代码到底花了多少时间?”并定位任意深层方法的方式。

[LOADING...]

帧工具提示

悬停在一个帧上会显示远不止样本数量:total (总样本) 与 self (自身样本)(通过该帧所花费的时间 vs 直接在该帧内的时间)、其字节码索引和源代码行号,以及一个 compilation breakdown (编译分解)——JIT 编译、C1 编译或内联——揭示该方法实际是如何执行的。Open in IDEView Source 可直接跳转到代码,前提是 Microscope 已与 Jeffrey IntelliJ 插件 配对使用。

Copy for AI (复制给 AI)

Copy for AI 按钮会将当前视图(堆栈、权重和热点路径)导出为紧凑的 Markdown 摘要,复制到剪贴板或下载为 .md 文件。你可以将其粘贴到例如 Claude Code 中,让 AI 根据火焰图中的运行时性能分析结果来优化你的代码。

其他火焰图

上述所有功能不仅适用于 CPU。回到 Primary 页面,你可以用同样的方式打开 AllocationWall-Clock 火焰图——导航、搜索、工具提示和范围选择器均相同——但每个火焰图回答不同的问题:

  • Wall-Clock – 墙钟时间花费在哪里,包括等待时间,而不仅仅是 CPU 工作时间。
  • Allocation – 内存在哪里分配。

有两个值得尝试的渲染选项:

  • Use Thread-mode – 按线程拆分图表,显示每个线程的调用树,而不是合并视图。当某个线程占主导或行为异常时非常有用。

[LOADING...]

  • Use Total Allocation – 将分配图从 样本数量 切换为 权重:每个帧的大小由分配的字节数决定,而不是由采样命中的次数决定。这样,一个很少被采样但分配了大量对象的路径就会以真实成本显示出来。根据事件自身的度量而非样本数量进行加权,往往会呈现出一幅截然不同且更可操作的画面。

[LOADING...] [LOADING...]

总结

在本文中,我们设置了 Jeffrey Microscope,并逐步讲解了如何阅读火焰图——时间序列和范围选择器、搜索、帧工具提示、Copy for AI 导出,以及分配和墙钟变体。这些已经足够让你找到应用程序花费时间的地方,并开始有信心地进行优化。

感谢阅读!如需深入了解,请访问 Jeffrey 官网,或通过 LinkedIn 直接联系我——我很乐意听取你的反馈。敬请期待:在下一篇文章中,我们将并排比较这两个录制文件,展示 Jeffrey 的 Differential (差分) 火焰图如何精准定位优化和未优化代码之间的差异。

DZone 贡献者所表达的观点均属其个人。