Java Flight Recorder

Java Flight Recorder (JFR) 是一种用于收集、诊断和分析有关正在运行的 Java 应用程序的数据的工具。它集成到Java虚拟机(JVM)中,几乎不会造成性能开销,因此即使在负载较重的生产环境中也可以使用。使用默认设置时,性能影响不到百分之一。对于某些应用程序,它可能会低得多。但是,对于短时间运行的应用程序(不是在生产环境中运行的应用程序类型),相对启动和预热时间可能会更长,这可能会影响性能超过 1%。JFR 收集有关 JVM 以及在其上运行的 Java 应用程序的数据。

我们都知道,黑匣子是用于记录飞机飞行和性能参数的仪器。在飞机出问题后,用于定位问题原因。JFR 就是 Java 的黑匣子。

JFR 是 Java Flight Record (Java飞行记录) 的缩写,是 JVM 内置的基于事件的JDK监控记录框架。这个起名就是参考了黑匣子对于飞机的作用,将Java进程比喻成飞机飞行。顾名思义这个记录主要用于问题定位和持续监控。最主要的是它从JDK11开始不再需要商业许可,只要是Hotspot的jdk都可以使用。

JFR的优点

  • 提供更好的数据: JFR 从运行时的各个部分捕获数据,并付出了巨大的努力来确保捕获的数据代表系统的真实状态。这种努力的例子包括最大限度地减少观察者效应,以及能够在安全点之外捕获样本。

  • 提供更好的数据模型:数据模型是自描述的。录音无论大小,都包含理解数据所需的一切。

  • 提供更好的性能:飞行记录仪引擎本身针对性能进行了优化,不会对应用的性能产生负面影响。一些数据实际上可以免费获得,因为它已经被运行时捕获。

  • 允许第三方事件提供程序:一组 API 使 JFR 能够从第三方应用程序(包括 WebLogic Server 和其他 Oracle 产品)捕获数据。

  • 降低总体拥有成本: JFR 使我们能够减少诊断和排除问题的时间,减少运营成本和业务中断,在出现问题时提供更快的解决时间,并提高系统效率。

JFR原理

事件

Java Flight Recorder 收集有关事件的数据。事件在特定时间点发生在 JVM 或 Java 应用程序中。每个事件都有一个名称、一个时间戳和一个可选的有效负载。负载是与事件相关的数据,例如CPU使用率、事件前后的Java堆大小、锁持有者的线程ID等。

大多数事件还包含有关事件发生的线程、事件发生时的堆栈跟踪以及事件持续时间的信息。使用事件中可用的信息,我们可以重建 JVM 和 Java 应用程序的运行时详细信息。

JFR 收集有关三类事件的信息:

持续时间事件需要一些时间才能发生,并在完成时记录。我们可以设置持续时间事件的阈值,以便仅记录持续时间超过指定时间段的事件。这对于其他类型的事件是不可能的。即时事件立即发生,并立即记录。

定期记录样本事件(也称为可请求事件),以提供系统活动的样本。我们可以配置采样发生的频率。

JFR 以极高的细节水平监控运行系统。这会产生大量数据。为了使开销尽可能低,请将记录事件的类型限制为我们实际需要的类型。在大多数情况下,持续时间非常短的事件是没有意义的,因此将记录限制为持续时间超过某个有意义阈值的事件。

事件流(k8s监控特别适合)

JFR 从 JVM(通过内部 API)和 Java 应用程序(通过 JFR API)收集数据。该数据存储在小型线程本地缓冲区中,这些缓冲区会刷新到全局内存缓冲区中。然后全局内存缓冲区中的数据被写入磁盘。磁盘写入操作的成本很高,因此我们应该通过仔细选择启用记录的事件数据来尽量减少磁盘写入操作。二进制记录文件的格式非常紧凑,并且对于应用程序的读写来说非常高效。每个缓冲区之间没有信息重叠。特定的数据块可以在内存或磁盘上使用,但不能同时在两个地方使用。这具有以下含义:
如果发生电源故障,尚未刷新到磁盘缓冲区的数据将不可用。

JVM 崩溃可能会导致一些数据在核心文件(即内存缓冲区)中可用,而另一些数据则在磁盘缓冲区中可用。JFR 不提供合并此类缓冲区的功能。

在 JFR 收集的数据在我们使用之前,可能会有一小段延迟(例如,必须将其移动到不同的缓冲区才能使其可见,记录文件中的数据可能不按时间顺序排列,因为数据是从多个线程缓冲区以块的形式收集的。

在某些情况下,JVM 会删除事件顺序以确保它不会崩溃。任何无法足够快地写入磁盘的数据都会被丢弃。发生这种情况时,记录文件将包含受影响时间段的信息。此信息还将记录到 JVM 的日志记录工具中。

我们可以将 JFR 配置为不将任何数据写入磁盘。在此模式下,全局缓冲区充当循环缓冲区,当缓冲区已满时,最旧的数据将被丢弃。这种开销非常低的操作模式仍然可以收集根本原因问题分析所需的所有重要数据。由于最新数据始终在全局缓冲区中可用,因此只要操作或监视系统检测到问题,就可以按需将其写入磁盘。然而,在这种模式下,只有最后几分钟的数据可用,因此它只包含最近的事件。如果我们需要获取长时间内的完整操作历史记录,在定期将事件写入磁盘时使用默认模式。

使用

我们使用它可以在启动的时候通过jvm参数指定,也可以对已经启动的应用通过jcmd启动,也可以通过jmc的页面配置

jvm 参数指定

  • idea 启动时候我们增加如下参数
    -XX:+FlightRecorder 启动jfr
    -XX:StartFlightRecording=delay=20s,duration=60s,filename=my.jfr 表示启动应用之后延迟20s,开始记录JFR信息60秒,数据保存在my.jfr中

    1
    -XX:+FlightRecorder -XX:StartFlightRecording=delay=20s,duration=60s,filename=my.jfr
  • 常用参数:
    name=name 指定记录的名称
    dumponexit=true/false JVM推出时是否记录事件,默认为 false
    settings=path JFR 配置文件的路径
    delay=time 开始记录前的延时
    duration=time 记录持续时间
    filename=path 保存记录事件的文件路径
    compress=true/false 是否使用 gzip 压缩记录数据,默认为 false
    maxage=time 环形缓存中保存记录的数据的最长时间
    maxsize=size 环形缓存占用的最大空间

  • idea控制台启动时候输出如下日志:

    1
    2
    3
    [0.198s][info][jfr,startup] Started recording 1. The result will be written to:
    [0.198s][info][jfr,startup]
    [0.198s][info][jfr,startup] /Users/suaofeng/IdeaProjects/spring-boot-starter/my.jfr

jcmd启动

这种是可以在任意情况下使用的,即使你忘记了启动时启动jfr,通过它依旧可以给进程添加一个记录器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
➜  spring-boot-starter git:(master) ✗ jps -l
57122 org.jetbrains.jps.cmdline.Launcher
57123 sample.mybatis.web.SampleWebApplication
94082
55349
56230 org.jetbrains.idea.maven.server.RemoteMavenServer36
57133 jdk.jcmd/sun.tools.jps.Jps
➜ spring-boot-starter git:(master) ✗ jcmd 57123 JFR.start name=jfr-test1 disk=true dumponexit=true filename=my2.jfr maxage=2m maxsize=128M
57123:
Started recording 2.

Use jcmd 57123 JFR.dump name=jfr-test1 to copy recording data to file.
➜ spring-boot-starter git:(master) ✗ jcmd 57123 JFR.check
57123:
Recording 2: name=jfr-test1 maxsize=128.0MB maxage=2m (running)
➜ spring-boot-starter git:(master) ✗ jcmd 57123 JFR.stop name=jfr-test1
57123:
Stopped recording "jfr-test1".

jmc启动

Java Mission Control分析

扩展JFR事件

继承jdk.jfr.Event自定义事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Example {

@Label("Hello World")
@Description("Helps programmer getting started")
static class HelloWorld extends Event {
@Label("Message")
String message;
}

public static void main(String... args) {
HelloWorld event = new HelloWorld();
event.message = "hello, world!";
event.commit();
}
}

也可以通过jfr命令配置事件。


Java Flight Recorder
https://vegetablest.github.io/2022/08/25/JFR/
作者
af su
发布于
2022年8月25日
许可协议