1 什么是JFR
Java Flight Recorder(JFR)是在Java应用程序运行时,收集诊断与分析数据的工具。它集成在Java虚拟机(JVM)中并且几乎没有性能开销,所以它甚至可以用在高负载的生成环境中。默认的情况下它是开启的,内部测试与用户反馈都表明:性能损耗低于百分之一。对于一些应用程序,它可能表现得现低。然而,对于短时运行的应用程序(不是那种在生产环境运行的应用程序),相对于关联启动与预热时间可以会大一点,可以对性能的影响会超过1%。JFR收集JVM与运行在JVM上的Java应用程序的数据。
相较于其他工具,JFR有下列的优点:
- 提供优良的数据:JFR通过使用一致的数据模型使它很容易的交叉引用对象与过滤事件。
- 允许第三方事件提供者:一组API,允许JFR监控第三方应用程序,包括WebLogic服务器与其他Oracle的产品。
- 降低使用者的总开销:JFR让你花费很少的时间就可以诊断与排查问题,降低运营成本与业务中断,当问题发生时提供快速解决方案及提高系统效率。
JRE的主要作用:
- 资料收集:JFR持续保存有关于运行中的系统的大量的数据。收集的信息包括线程样本(),锁配置以及垃圾回收的详情。
- 黑盒分析:JFR持续保存信息到一个循环缓冲区。当一个异常被检测到时就可以访问这些信息,以查找到问题的原因。
- 支持与调试:当联系Oracle支持来帮助诊断Java应用程序的时候,通过JFR收集的数据是必不可少的。
1.1. 事件
Java Flight Recorder收集与事件有关的数据。事件发生在JVM或者Java应用程序中的一个特定的时间点。每一个事件都有一个名字、一个时间戳以及一个自定义的载荷。这个载荷是一个事件的相关数据,例如,CPU的用量、这个事件前后的JAVA堆大小,锁的持有者的线程ID等等。
事件信息还包括这个事件发生的线程的相关信息,事件发生时的堆栈跟踪以及事件的持续时间。合理的使用事件的信息,你可以重建JVM和Java应用程序的运行时的详细情况。
JFR收集关于事件的三种类型信息:
- 持续事件:发生需要花费一些时间,并且当它完成时被记录。你可以为持续事件设置一个阈值,以便,只记录持续时间超过这个特定的时间段的事件。
- 即时事件:立即发生,就会被马上记录。
- 示例事件:也称为请求型事件,定时记录(固定的间隔),提供一个系统活动的示例。你可以配置示例发生的频率。
JFR以一种极高层次的精细度监控着运行中的系统。这将产成大量数据。为了保持低开销,限制记录事件的类型,只记录你实际用到的。在大多数的场景中,很短期的事件可以不记录。
1.2. 数据流
JFR收集来自于JVM(通过内部API)以及来自Java应用程序(通过JFR的APIs)的数据。数据被存储在一个很小的线程本地缓冲区中,可以被刷新到全局内存缓冲区。全局内存缓冲区中的数据随后会被写入到磁盘中。磁盘写入操作是代价很高的,所以你应该慎重的选择你需要记录的事件数据,使其尽可能的最小化。二进制的记录文件的格式是非常小且高效的,它便于应用程序的读与写。
不同的缓冲区之间没有信息的重叠。一个特定的数据块存入在内存或者磁盘中都是合理的,但是它不能同时存在于内存与磁盘。这有着下面的意义:
- 在电源发生故障时,还没有被刷新到磁盘缓冲区中的数据是无效的。
- JVM崩溃可能导致一部分数据在核心文件中(也就是,内存中的缓冲区),并且一些在磁盘缓冲区中。JFR不提供将合并这些缓冲区的能力。
- 数据在被JFR收集提供给你你以前可能会有一个很小的延迟是合理的。例如,在使数据可见以前可能不得不把它们移动到其他的缓冲区。
- 记录到文件中的数据在时间上可能是不连续的,因为这些数据是从各种各样的线程缓冲区收集来的。
在某些情况下,JVM会删除事件,以确保它不会崩溃。任何没有被快速写入到磁盘中的数据都会被丢弃。如果这种情况发生,在这个时间段内,记录文件所包含的信息会胎神到影响。这些信息也可能被记录到JVM的日志工具中。
你可以配置JFR不将数据写入到磁盘中。对于这种模式中,全局缓冲区扮演着一个循环缓冲区的角色,老的数据会在缓冲区满了以后被删除。这个非常低开销的操作模式依然收集所有重要的数据,用于分析根本问题之需。因为最新的数据在全局缓冲区中一直是有效的,无论何时,只要操作或监控系统检测到问题,它都可以根据需要写入到磁盘中。然而,在这种模式下,只有最近几分钟的数据是有效的,所以它只包含最近的事件。如果你需要获得一个段时间内的全部的历史,使用默认模式的时候,事件可以被定期的写入到磁盘中。
1.3. Java Flight Recorder体系架构
JFR是由下列的组件构成的:
1、JFR运行时(JFR runtime)
JFR运行时是JVM内部的一个记录引擎,用于生成记录。这个运行时引擎是由下列的组件构成的:
- 代理:控制缓冲区,磁盘I/O、MBean等等。这个组件提供了一个用C和Java代码编写的动态库,并且还提供独立于JVM的纯Java实现。
- 生成者:将数据插入到缓冲区。它们可以收集来自于JVM和Java应用程序中的事件,包括来自于第三方的应用程序。(通过Java的API)
2、飞行记录插件:即Flight Recorder plugin,用于Java Mession Control(JMC),可以让你通过JMC的客户端来使用JFR,用一个图形化的用户界面(GUI)去启动、关闭和配置记录,同样展示记录文件。
1.4. 怎么使用JFR
JFR是一个实验性的功能,因此他的使用受制于变化。事实上,在早期的发布版本中,我们在产品中使用就必须激活商业特性。然而,从JDK11开始,我们可以不用受到任何限制的使用它。
对于JDK 8,为了激活JFR,我们需要在开始运行JVM的时候使用参数:+UnlockCommercialFeatures和+FlightRecorder。
当我们在运行Java应用程序的同时激活JFR,我们可以使用命令行来进行。但是对于已经运行的应用程序,我们可以使用诊断命令工程。
1、使用命令行工具
JDK8
java -Xms50m -Xmx50m -XX:+AlwaysPreTouch -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=200s,filename=flight.jfr -jar .\target\jfr-hello-1.0-SNAPSHOT-full.jar
JDK9+
java -Xms50m -Xmx50m -XX:+AlwaysPreTouch -XX:StartFlightRecording=duration=200s,filename=flight.jfr -jar jfr-hello-1.0-SNAPSHOT-full.jar
这条命令启动应用程序并且激活JFR,立即开始记录并且持续不超过200秒。收集的数据保存到文件flight.jfr文件中。
2、使用诊断命令行工具
这个jcmd命令是用于给JVM发送诊断命令请求的,这些请求对于控制Java Flight Recorder、解决问题以及诊断JVM和Java应用程序是很有用的。这个命令必须与正在运行中的JVM在同一台机器上,如果是类Linux操作系统,该命令还需要与JVM具有相同的用户及组权限标识。
命令格式:jcmd <进程ID/主函数> <command>[options]
jcmd jfr-hello-1.0-SNAPSHOT-full.jar JFR.start duration=200s filename=flight.jfr
对已经运行中的jfr-hello-1.0-SNAPSHOT-full.jar(在Linux下可使用进程号)的开启JFR。
注意:由于JDK8以前需要开启商业特性,所以要求使用该诊断工具的Java程序,必须以开启商业特性的方式运行。
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -jar jfr-hello-1.0-SNAPSHOT-full.jar
jcmd jfr-hello-1.0-SNAPSHOT-full.jar JFR.start duration=200s filename=flight.jfr
该命令的其他使和方式见文档The jcmd Utilit。
2 什么是JMC
2.1. 概述
JDK Mission Control,简称JMC。
Java Flight Recorder和JDK Mission Control一起构建了一个完整的工具链,来持续性的收集底层的,并且详情的运行时信息,用于事后分析。Java Fligth Recorder是一个构建于Oracle JDK之上的分析与事件收集的框架,他允许Java管理者和开发者采集详细的关于Java虚拟机(JVM)和Java应用程序的底层信息。JDK Mission Control是一个高级的工具集,可以高效且详尽的分析通过Java Flight Recorder收集的各种数据。这个工具链可以让开发者和管理者收集和分析来自本地或发布于生产环境中的Java应用程序的数据。
2.2. 下载与安装
下载地址:
https://www.oracle.com/java/technologies/jdk-mission-control.html
下载后,直接运行即可。目前版本8,可以分析JDK7以上记录。
2.3 分析上述产生的JFR记录
上述的jfr-hello-1.0-SNAPSHOT-full.jar中只有一个主类——JFRApplication.java,该类的目的:
- 创建一个生产者,一个消费者
- 生产者每10ms生成一个1MB的二进制数组作为产品
- 消费者每100ms消费一个1MB的二进制的数组
- 随着时间的推移,产品逐渐堆积,而存放产品的队列并没有限制长度
- 生产者堆积到一个临界点,生成产品(1MB的字节数组)时无法分配内存导致内存溢出
- 由于无法分配内存,此时的消费者也无法再消费产品(队列take的时候也会失败)
- 整个程序处于宕机状态,队列中的剩余产品也无法再被消费。
- 报错:Exception in thread “Producer Thread” java.lang.OutOfMemoryError: Java heap space。
public class JFRApplication {
private static BlockingQueue<byte[]> queue = new LinkedBlockingQueue<>();
public static void main(String[] args) {
Runnable producer = () -> {
while (true) {
// generates 1mb of object every 10ms
queue.offer(new byte[1 * 1024 * 1024]);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable consumer = () -> {
while (true) {
try {
// process every 100ms
queue.take();
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// give a name, good for profiling
new Thread(producer, "Producer Thread").start();
new Thread(consumer, "Consumer Thread").start();
}
}
使用jmc界面,选择菜单 文件 -> 打开文件,选择flight.jfr文件,图示如下:

从上面的图中可以看到,从很多层面都可以跟踪到程序的运行状况,比如I/O、线程、内存等等。