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.jfr
和jdk.management
Flight Recorder 可以在命令行上启动:
$ java -XX:StartFlightRecording ...
此外,还可以使用 bin/jcmd 工具启动和控制记录:
$ jcmd <pid> JFR.start
$ jcmd <pid> JFR.dump filename=recording.jfr
$ jcmd <pid> JFR.stop
2
3
通过 JMX 远程提供此功能,对诸如 Mission Control 之类的工具很有用。
生成和消费事件
用户可以使用 API 创建自己的事件:
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();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
可以使用 jdk.jfr.consumer
包中提供的类从记录文件中提取数据:
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"));
}
2
3
4
5
6
7
缓冲区机制和二进制数据格式
线程以无锁的方式将事件写入线程本地缓冲区。一旦线程本地缓冲区填满,它将被提升到一个全局内存中的循环缓冲区系统,该系统维护最新的事件数据。根据配置,最旧的数据要么被丢弃,要么被写入磁盘,从而允许历史数据被连续保存。磁盘上的二进制文件具有 .jfr
扩展名,并使用保留策略进行维护和控制。
事件模型使用自描述的二进制格式实现,以 128 位小端编码(除了文件头部和一些附加部分)。二进制数据格式不应直接使用,因为它可能会发生变化。相反,将提供 API 用于与记录文件交互。
作为一个说明性示例,类加载事件包含一个描述其发生时间的时间戳、描述时间跨度的持续时间、线程、堆栈跟踪以及三个特定于事件的载荷字段,即加载的类和相关的类加载器。事件的总大小为 24 字节。
<内存地址>: 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 将源代码移至开放存储库,以使该功能普遍可用。因此,对兼容性、性能、回归和稳定性的风险很低。