JEP 328:飞行记录仪
概括
提供一个低开销的数据收集框架,用于对 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
可以在命令行启动飞行记录器:
$ java -XX:StartFlightRecording ...
还可以使用 bin/jcmd 工具启动和控制记录:
$ jcmd <pid> JFR.start
$ jcmd <pid> JFR.dump filename=recording.jfr
$ jcmd <pid> JFR.stop
此功能通过 JMX 远程提供,对于任务控制等工具很有用。
生产和消费事件
有一个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
,并使用保留策略进行维护和控制。
事件模型以自描述二进制格式实现,以小端基 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]
- 加载类:
配置和过滤事件
可以启用、禁用和过滤事件,以减少开销和存储所需的空间量。这可以使用以下设置来完成:
- 启用 - 是否应该记录事件
- 阈值 - 低于该时间的事件不会被记录
- stackTrace - 是否应记录来自 Event.commit() 方法的堆栈跟踪
- period - 事件发出的时间间隔(如果是周期性的)
有两个配置集专门用于配置飞行记录器,以实现低开销、开箱即用的用例。用户可以轻松创建自己的特定事件配置。
操作系统、JVM 和 JDK 库事件
将添加涵盖以下领域的活动:
- 操作系统
- 内存、CPU 负载和 CPU 信息、本机库、进程信息
- 虚拟机
- 标志、GC 配置、编译器配置
- 方法分析事件
- 内存泄漏事件
- JDK 库
- 套接字 IO、文件 IO、异常和错误、模块
备择方案
飞行记录器的替代方案是日志记录。尽管JEP 158:统一 JVM 日志记录在 HotSpot JVM 中的子系统之间提供了一定程度的一致性,但它并未扩展到 Java 应用程序和 JDK 库。传统上,日志记录通常缺乏显式模型和元数据,使其形式自由,因此消费者必须与内部格式紧密耦合。如果没有关系模型,就很难保持数据紧凑和规范化。
Flight Recorder 维护一个类型化的事件模型,其中消费者通过使用 API 与内部分离。
测试
需要进行性能测试以确保可接受的开销水平。
风险和假设
特定于供应商的后端可能是在 JEP 167 之上开发的;工作假设是飞行记录器基础设施应该涵盖大多数现有用例。我们鼓励供应商在此 JEP 的背景下参与讨论,讨论按照建议迁移到单一后端的可行性。
Flight Recorder 已经存在很多年了,之前是 Oracle JDK 的一个商业功能。此 JEP 将源代码移至开放存储库,以使该功能普遍可用。因此,兼容性、性能、回归和稳定性的风险很低。