Skip to content

JEP 328: Flight Recorder | 飞行记录器

摘要

为 Java 应用程序和 HotSpot JVM 提供低开销的数据收集框架,用于故障排查。

目标

  • 提供用于生成和消费数据的 API,以事件形式
  • 提供缓冲机制和二进制数据格式
  • 允许配置和过滤事件
  • 提供操作系统、HotSpot JVM 和 JDK 库的事件

非目标

  • 提供收集数据的可视化或分析
  • 默认启用数据收集

成功指标

  • 在 SPECjbb2015 基准测试中,默认情况下最多带来 1% 的性能开销
  • 未启用时,无可测量的性能开销

动机

故障排查、监控和性能分析是开发周期中不可或缺的部分,但某些问题仅在涉及真实数据的生产环境中,在重负载下才会发生。

Flight Recorder 记录来自应用程序、JVM 和操作系统的事件。事件被存储在一个单独的文件中,该文件可以附加到错误报告中,并由支持工程师进行检查,允许在问题发生前的一段时间内对问题进行事后分析。工具可以使用 API 从记录文件中提取信息。

描述

JEP 167: 基于事件的 JVM 追踪 为 HotSpot JVM 添加了一组初始事件。Flight Recorder 将把创建事件的能力扩展到 Java。

JEP 167 还添加了一个基础后端,该后端将事件数据打印到标准输出。Flight Recorder 将提供一个高性能的后端,用于以二进制格式写入事件。

模块:

  • jdk.jfr
    • API 和内部实现
    • 仅需要 java.base(适用于资源受限的设备)
  • jdk.management.jfr
    • JMX 功能
    • 需要 jdk.jfrjdk.management

Flight Recorder 可以在命令行上启动:

bash
$ java -XX:StartFlightRecording ...

此外,还可以使用 bin/jcmd 工具启动和控制记录:

bash
$ jcmd <pid> JFR.start
$ jcmd <pid> JFR.dump filename=recording.jfr
$ jcmd <pid> JFR.stop

通过 JMX 远程提供此功能,对诸如 Mission Control 之类的工具很有用。

生成和消费事件

用户可以使用 API 创建自己的事件:

java
import jdk.jfr.*;

@Label("Hello World")
@Description("帮助程序员入门")
class HelloWorld extends Event {
   @Label("消息")
   String message;
}

public static void main(String... args) throws IOException {
    HelloWorld event = new HelloWorld();
    event.message = "hello, world!";
    event.commit();
}

可以使用 jdk.jfr.consumer 包中提供的类从记录文件中提取数据:

java
import java.nio.file.*;
import jdk.jfr.consumer.*;

Path p = Paths.get("recording.jfr");
for (RecordedEvent e : RecordingFile.readAllEvents(p)) {
   System.out.println(e.getStartTime() + " : " + e.getValue("message"));
}

缓冲区机制和二进制数据格式

线程以无锁的方式将事件写入线程本地缓冲区。一旦线程本地缓冲区填满,它将被提升到一个全局内存中的循环缓冲区系统,该系统维护最新的事件数据。根据配置,最旧的数据要么被丢弃,要么被写入磁盘,从而允许历史数据被连续保存。磁盘上的二进制文件具有 .jfr 扩展名,并使用保留策略进行维护和控制。

事件模型使用自描述的二进制格式实现,以 128 位小端编码(除了文件头部和一些附加部分)。二进制数据格式不应直接使用,因为它可能会发生变化。相反,将提供 API 用于与记录文件交互。

作为一个说明性示例,类加载事件包含一个描述其发生时间的时间戳、描述时间跨度的持续时间、线程、堆栈跟踪以及三个特定于事件的载荷字段,即加载的类和相关的类加载器。事件的总大小为 24 字节。

txt
<内存地址>: 98 80 80 00 87 02 95 ae e4 b2 92 03 a2 f7 ae 9a 94 02 02 01 8d 11 00 00
  • 事件大小 [98 80 80 00]
  • 事件 ID [87 02]
  • 时间戳 [95 ae e4 b2 92 03]
  • 持续时间 [a2 f7 ae 9a 94 02]
  • 线程 ID [02]
  • 堆栈跟踪 ID [01]
  • 载荷 [ 字段 ]
    • 加载的类: [0x8d11]
    • 定义类加载器: [0]
    • 初始化类加载器: [0]

配置和过滤事件

事件可以被启用、禁用和过滤,以减少开销和所需的存储空间。这可以通过以下设置来实现:

  • enabled - 是否应记录该事件
  • threshold - 事件持续时间低于此阈值则不记录
  • stackTrace - 是否记录从 Event.commit() 方法获取的堆栈跟踪
  • period - 如果是周期性事件,则指定该事件的发出间隔

有两组配置集特别用于为低开销、开箱即用的用例配置 Flight Recorder。用户可以轻松地创建他们自己的特定事件配置。

操作系统、JVM 和 JDK 库事件

将添加覆盖以下领域的事件:

  • 操作系统(OS)
    • 内存、CPU 负载和 CPU 信息、本地库、进程信息
  • Java 虚拟机(JVM)
    • 标志、GC 配置、编译器配置
    • 方法分析事件
    • 内存泄漏事件
  • JDK 库
    • 套接字 I/O、文件 I/O、异常和错误、模块

替代方案

Flight Recorder 的一个替代方案是日志记录。尽管 JEP 158: Unified JVM Logging(统一的 JVM 日志记录)为 HotSpot JVM 的各个子系统提供了一定程度的统一性,但它并没有扩展到 Java 应用程序和 JDK 库。传统上,日志记录通常缺乏明确的模型和元数据,因此它采用自由格式,导致消费者必须与内部格式紧密耦合。没有关系模型,很难保持数据的紧凑和规范化。

Flight Recorder 维护了一个类型化的事件模型,消费者通过使用 API 与内部机制解耦。

测试

需要进行性能测试以确保可接受的开销水平。

风险和假设

基于 JEP 167 可能已经开发了特定于供应商的后端;工作假设是 Flight Recorder 基础设施应该能够覆盖大多数现有用例。鼓励供应商在此 JEP 的上下文中参与讨论,探讨向单一后端迁移的可行性。

Flight Recorder 已经存在多年,并且之前是 Oracle JDK 的商业特性。此 JEP 将源代码移至开放存储库,以使该功能普遍可用。因此,对兼容性、性能、回归和稳定性的风险很低。