跳到主要内容

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