火焰图成为调查当今应用程序(不仅仅是用 Java 编写)问题的事实标准。火焰图可以提供许多有趣的见解,并可以为开发人员提供有价值的提示,以改进其应用程序的执行。然而,仍然有很多开发人员不使用它们,即使生成火焰图比以往任何时候都容易。
在本文中,我将演示几个示例,说明在 Java 中开始调查应用程序是多么容易。如果您不了解有关火焰图可视化的详细信息,请访问Brendan Gregg撰写的一篇关于火焰图的精彩文章。
让我们开始使用 Jeffrey CLI
我将使用 Jeffrey CLI(命令行界面)工具来生成火焰图。对于您的终端来说,这是一个非常新且易于使用的工具,它接受JFR(JDK Flight Recorder)记录,并从存储在二进制文件中的事件中生成火焰图。
Jeffrey 获取 JFR 文件并将包含的图形和数据生成到一个 HTML 文件中,因此,您有一种非常方便的方式与同事共享它。
为了完整起见,我需要提一下还有 Jeffrey App。这是一个基于 Web 的解决方案,具有正在运行的 Java 后端,动态提供数据,并在 Jeffrey CLI 工具上包含一些其他有趣的功能。但是,今天让我们只关注生成火焰图。
基于服务器的解决方案 Jeffrey App 存在一些限制,这些限制来自生成文件的“静态性质”。Jeffrey App 在后台在服务器上动态生成数据。以下功能在 CLI 解决方案中不可用。但是,有一些针对这些缺点的缓解措施:
未提供时间序列图中的动态搜索。我们可以使用 CLI 参数在执行命令时分割时间序列图。
时间序列图的缩放不会传播到火焰图。
如果您从未听说过 JFR 录音,那么让我们从简要介绍开始。JFR 代表 JDK Flight Recorder,它是一种内置功能,用于在 OpenJDK 构建中从 JVM 和 Java 应用程序收集事件。它使用所有收集的事件 + 元数据生成优化的二进制文件,以正确解析文件中的事件。在撰写本文时,有两种常见的方法来生成此二进制文件:
直接在 JVM 中启用该功能(JDK Flight Recorder 用户指南)
使用 Async Profiler,它可以用自己的事件替换某些事件(Async-Profiler 的设置)
无论采用哪种方法生成的二进制文件都具有完全相同的结构,并且可以由 Jeffrey CLI 处理。
下载并启动
最直接的方法是直接从 GitHub 下载。
我将使用 Jeffrey 的测试应用程序中预生成的录音。检查录音并将jeffrey-cli.jar复制到同一文件夹,以使命令更简单、更短。上述 GitHub 存储库中的记录是使用 AsyncProfile 生成的,AsyncProfile 使用自己的 CPU () 和分配 () 事件,这些事件将在下面的示例中使用。jdk.ExecutionSamplejdk.ObjectAllocationInNewTLAB
jeffrey-cli.jar 只是一个可执行的 jar 文件,它执行带有一些参数的基本命令来指定行为。
根据 ,在撰写这篇博文时,我们可以生成火焰图、亚秒图及其微分图(比较 JFR 记录中相同事件类型之间的比较)。在本文中,让我们只关注火焰图。HELP command
$ java -jar jeffrey-cli.jar --help
Usage: [-hV] [COMMAND]
Generates a flamegraph according to the selected event-type
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
Commands:
flame Generates a Flamegraph (default: jdk.ExecutionSample)
flame-diff Generates a Differential Flamegraph (default: jdk.ExecutionSample)
events List all event-types containing a stacktrace for building a flamegraph
sub-second Generates Sub-Second graph (the first 5 minutes)
sub-second-diff Generates Differential Sub-Second graph (the first 5 minutes)
具有默认事件类型的火焰图
有多个参数可以澄清生成的输出,让我们关注主要参数。执行命令以显示以下信息。flame --help
$ java -jar jeffrey-cli.jar flame --help
Usage: flame [-htVw] [--with-timeseries] [-e=<eventType>] [--end-time=<endTime>] [-o=<outputFile>] [-s=<searchPattern>] [--start-time=<startTime>] <jfr_file>
Generates a Flamegraph (default: jdk.ExecutionSample)
<jfr_file> one JFR file for fetching events
-e, --event-type=<eventType>
Selects events for generating a flamegraph (e.g. jdk.ExecutionSample)
--end-time=<endTime> Relative end in milliseconds from the beginning of the JFR file
-h, --help Show this help message and exit.
-o, --output=<outputFile> Path to the file with the generated flamegraph (default is the current folder with a filename '<jfr-name>.html')
-s, --search-pattern=<searchPattern>
Only for timeseries (timeseries cannot dynamically searches in the generated file, only the flamegraph can)
--start-time=<startTime>
Relative start in milliseconds from the beginning of the JFR file
-t, --thread Groups stacktraces omitted on the particular thread
-V, --version Print version information and exit.
-w, --weight Uses event's weight instead of # of samples (currently supported: jdk.ObjectAllocationSample, jdk.ObjectAllocationInNewTLAB, jdk.
ObjectAllocationOutsideTLAB, jdk.ThreadPark, jdk.JavaMonitorWait, jdk.JavaMonitorEnter)
--with-timeseries Includes Timeseries graph with a Flamegraph (it's `true` by default, set `false` to have only the Flamegraph)
下面最简单的命令将 CPU 火焰图(基于 )生成到与记录名称相同的文件夹中(您可以使用参数来指定输出的文件名和路径)。jdk.ExecutionSampleoutput
java -jar jeffrey-cli.jar flame jeffrey-persons-full-direct-serde.jfr
Generated: <path>/jeffrey-recordings/jeffrey-persons-full-direct-serde.html
在您最喜欢的浏览器中打开
特定事件类型的火焰图
下面的另一个示例使用带有选项的特定事件类型。由于我们知道记录是使用带有选项的 async-profiler 生成的,因此我们需要使用 as the 来获得适当的结果。weightallocjdk.ObjectAllocationInNewTLABevent-type
weight 选项在这种情况下很有用,因为我们希望更多地关注生成更多字节的路径,而不是更多样本。否则,我们可能会被大量样本所误导,这些样本分配的内存量不大,这些样本会隐藏样本较少但分配块巨大的有趣点。
$ java -jar jeffrey-cli.jar flame --event-type=jdk.ObjectAllocationInNewTLAB --weight jeffrey-persons-full-direct-serde.jfr
Generated: <path>/jeffrey-recordings/jeffrey-persons-full-direct-serde.html