Java性能优化(二)——JFR(Java Flight Recorder)使用教程

概念

JFR(Java Flight Recorder,下文简称JFR)数据是JVM的历史事件,用来诊断JVM的历史性能和操作。是一种监控工具,可以在Java应用程序执行期间收集有关JVM事件的信息。JFR会开启一组事件,当有对应的事件发生时,就会保留相应的数据到文件中或者内存中(假如有开启缓存池),JMC可以显示这些事件——实时从JVM获取或者从文件中获取,JMC可以展示详细的JFR记录的数据。JFR对于被监控的应用程序来说,默认设置的性能开销很低:程序性能的1%以下,但是随着开始的事件或者记录线程增多,性能开销也会随之增多。

JFR有两种主要的概念:事件和数据流

事件

JFR在Java应用运行时收集对应发生的事件,主要有三种类型的事件提供给JFR收集:

  • 即时事件:一旦事件发生会立即进行数据记录
  • 持续事件:如果持续时间超过指定阈值则进行数据记录
  • 简单事件:用于记录应用所在系统的活跃指标(例如CPU,内存等)

数据流

JFR收集的事件包含大量数据,将这些数据保存在filename.jfr中,众所周知,磁盘I/O操作非常昂贵。因此,在将数据块刷新到磁盘之前,JFR使用各种缓存来存储收集的数据。因为加入缓存的原因,在某些情况下,JFR的数据存在丢失的可能性。如果发生丢失数据的情况,JFR会尝试通知输出文件,丢失了一部分的信息。

前期准备

需要下载安装对应的Java Mission Control,自行下载即可,我本地安装了Java Mission Control 8(文章发布时的最新版),JMC可以同JFR结合使用,JMC可以将JFR生成的XXX.jfr文件可视化展示出来。数据展示非常丰富。在文章实战后面会详细解释。

使用

使用配置参数开启:

# 加上以下的这两个参数即可开启对应的JFR功能
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
复制代码

在Tomcat中配置对应的参数不生效,也不知道是什么原因,如果有解决的小伙伴可以在评论区

使用命令行开启

使用jcmd命令行解锁JFR功能权限。

jcmd process_id VM.unlock_commercial_features 解锁JFR记录功能权限
复制代码

使用jcmd命令行开启一个记录线程,duration记录的时间段,默认为0s,代表无限制,以下代码使用200s,表示记录200s结束。filename表示保存的文件名。

jcmd激活 jcmd process_id JFR.start duration=100s filename=flight.jfr (JDK 11版本前需要先激活对应的功能)
复制代码

命令行详解

jcmd命令中包含了操作JFR的所有操作,现在对操作以及参数进行详细解释。
首先,假如你不了解一些命令行的使用规则,你可以使用jcmd help命令去了解对应命令行的使用解释。例如,查看一个JFR.check的命令行使用方式,指定对应的进程ID(本文中使用5361),使用如下命令行:

jcmd 5361 help JFR.check

# 命令行返回值
5361:
JFR.check
Checks running JFR recording(s)

Impact: Low

Permission: java.lang.management.ManagementPermission(monitor)

Syntax : JFR.check [options]

Options: (options must be specified using the <key> or <key>=<value> syntax)
	name : [optional] Recording name, e.g. \"My Recording\" or omit to see all recordings (STRING, no default value)
	recording : [optional] Recording number, or omit to see all recordings (JLONG, -1)
	verbose : [optional] Print event settings for the recording(s) (BOOLEAN, false)
复制代码

通过英文的意思,我们可以了解到各个参数的使用方式,以及对应含义和注意事项。
下面我们进入正题,与JFR关联的jcmd命令有以下四种:

  • JFR.start——启动一个新的JFR记录线程
  • JFR.check——检查正在运行的JFR记录线程
  • JFR.stop——停止一个指定的JFR记录线程
  • JFR.dump——拷贝一个指定的JFR记录线程的内容进入文件中

每个命令行都有对应的参数,现在一一介绍对应的参数详解

JFR.start

参数 说明 值类型 默认值
name 记录线程的名字 String
settings 服务端模版 String
defaultrecording 开始默认记录 Boolean False
delay 开始记录的延迟时间 Time 0s
duration 记录的时长 Time 0s(表示永远,不中断)
filename 记录的名称 String
compress 使用GZip压缩记录的结果文件 Boolean False
maxage 缓冲区数据的最长使用期限 Time 0s代表没有时间期限制
maxsize 缓存容量的最大数量 Long 0代表没有最大大小

JFR.check

参数 说明 值类型 默认值
name 记录线程的名字 String
recording 记录线程的ID值 Long 1
verbose 是否打印详细数据信息 Boolean False

JFR.stop

参数 说明 值类型 默认值
name 记录线程的名字 String
recording 记录线程的ID值 Long 1
discard 抛弃记录数据 Boolean
copy_to_file 拷贝记录数据到文件 String
compress_copy GZip压缩“copy_to_file”的文件 Boolean False

JFR.dump

参数 说明 值类型 默认值
name 记录线程的名字 String
recording 记录线程的ID值 Long 1
copy_to_file 拷贝记录数据到文件 String
compress_copy GZip压缩“copy_to_file”的文件 Boolean False

实战

介绍完命令行的使用方式以及参数详解,现在我们就来进行实战使用JFR。这里有一个注意点就是,虽然JFR被设计为在JVM和应用程序的性能的影响会小,但是最好设置持续时间(duration),缓冲区数据的最长使用期限(maxage),限制收集的最大数据量(maxsize)。
首先定义个一个内存泄露的主程序,具体代码如下:

public static void main(String[] args) {
    List<Object> items = new ArrayList<>(1);
    try {
        while (true){
            items.add(new Object());
        }
    } catch (OutOfMemoryError e){
        System.out.println(e.getMessage());
    }
    assert items.size() > 0;
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        System.out.println(e.getMessage());
    }
}
复制代码

使用启动命令:

java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder 
  -XX:StartFlightRecording=duration=200s,filename=flight.jfr 
复制代码

当项目启动完成,并发生 OutOfMemoryError 异常后,可以在目录下会发现一个名为flight.jfr的文件,将该文件拖至JDK Mission Control中,会自动进行分析。下图展示JDK Mission Control分析结果:
image.png
现在我们来具体分析这张图的结果:

  • 图片右侧红色的分数表示,该次分析的重点关注的部分,展开后都会进行详细的描述造成原因,例如第一点Application Halts表示由于GC造成应用程序停顿时间过长,比例偏高。如果实在不想看,复制贴贴放进翻译软件中,也会明白大致的意思。
  • 图片的左侧的侧边栏表示每个分析块的详细记录,有内存,有GC,有I/O等等,有红色小感叹号的是重点需要查看的部分。例如我们查看内存这一块

image.png
从图中可以得知,应用程序内存的使用,在5s内迅速跑满,然后触发GC操作,是什么方法导致内存使用如此迅速呢?根据Method Profiling的分析结果,可以看出时ArrayList的拷贝操作导致,如下图:

image.png

由以上操作我们可以快速定位到内存溢出的方法块,其他块的使用大家可以自行进行分析的时候查看,都是可视化的界面非常方便。使用JFR的时候最后不要定义太大的时间块,或者需要切分小块的时间块进行分析,因为大的时间块,会导致JFR文件十分巨大,本地进行分析的时候,也会产生卡顿或者电脑内存不够,导致本地机子卡死,无法分析。

参考资料

Monitoring Java Applications with Flight Recorder
Java性能权威指南
JDK Mission Control

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享