跳到主要内容

JEP 328:飞行记录器(Flight Recorder)

QWen Max 中英对照 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.jfrjdk.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 将源代码移至开放存储库,以使该功能普遍可用。因此,对兼容性、性能、回归和稳定性的影响风险较低。