JEP 328:飞行记录器(Flight Recorder)
概述
提供一个用于排查 Java 应用程序和 HotSpot JVM 的低开销数据收集框架。
目标
- 提供用于生成和消费事件数据的 API
- 提供缓冲机制和二进制数据格式
- 允许对事件进行配置和过滤
- 为操作系统 (OS)、HotSpot JVM 和 JDK 库提供事件支持
非目标
- 提供收集数据的可视化或分析
- 默认启用数据收集功能
成功指标
- 在 SPECjbb2015 上开箱即用的性能开销最多为 1%
- 未启用时没有可测量的性能开销
动机
故障排查、监控和性能分析是开发生命周期中不可或缺的部分,但有些问题只有在涉及真实数据的高负载生产环境中才会出现。
飞行记录器会记录来自应用程序、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
此功能通过 JMX 远程提供,适用于 Mission Control 等工具。
生产和消费事件
有一个供用户创建自己的事件的 API:
import jdk.jfr.*;
@Label("Hello World")
@Description("Helps the programmer getting started")
class HelloWorld extends Event {
@Label("Message")
String message;
}
public static void main(String... args) throws IOException {
HelloWorld event = new HelloWorld();
event.message = "hello, world!";
event.commit();
}
可以使用 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"));
}
缓冲机制与二进制数据格式
线程将事件无锁地写入线程本地缓冲区。一旦线程本地缓冲区填满,它就会被提升到全局的内存循环缓冲区系统中,该系统维护最近的事件数据。根据配置,最旧的数据要么被丢弃,要么被写入磁盘,从而允许历史记录持续保存。磁盘上的二进制文件扩展名为 .jfr
,并使用保留策略进行维护和控制。
事件模型以自描述的二进制格式实现,采用小端序的 base 128 编码(文件头和一些附加部分除外)。二进制数据格式不应当直接使用,因为它可能会发生变化。取而代之的是,将提供用于与录制文件交互的 API。
作为一个示例,类加载事件包含一个时间戳,描述了事件发生的时间、一段描述时间跨度的持续时间、线程、堆栈跟踪以及三个特定于事件的有效负载字段——已加载的类和相关的类加载器。该事件的总大小为 24 字节。
<memory address>: 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 库
- 套接字输入输出 (Socket IO)、文件输入输出 (File IO)、异常与错误、模块
替代方案
Flight Recorder 的替代方案是日志记录。尽管 JEP 158: Unified JVM Logging 在 HotSpot JVM 的子系统中提供了一定程度的统一性,但它并未扩展到 Java 应用程序和 JDK 库。传统上,日志记录通常缺乏明确的模型和元数据,因此其形式较为自由,导致使用者必须紧密耦合到内部格式。没有关系模型的情况下,很难保持数据的紧凑性和规范化。
Flight Recorder 维护了一个类型化的事件模型,其中使用者通过使用 API 与内部结构分离。
测试
性能测试是必需的,以确保可接受的开销水平。
风险与假设
可能已经基于 JEP 167 开发了特定供应商的后端;目前的工作假设是,Flight Recorder 基础设施应该能够覆盖大多数现有的使用场景。鼓励供应商在本 JEP 的背景下讨论转向单一后端的可行性,如建议所述。
Flight Recorder 已经存在多年,之前是 Oracle JDK 的商业功能。此 JEP 将源代码移至开放存储库,以使该功能普遍可用。因此,对兼容性、性能、回归和稳定性的影响风险较低。