OpenJDK logo above a keyboard illustration

JDK Flight Recorder, or JFR, is an event-based production environment profiler available from OpenJDK 8u272 forward. Being a HotSpot-native feature, JDK Flight Recorder performs with extremely low overhead in terms of how it uses both space and time.

While JDK Flight Recorder collects basic Java runtime information by default, it is also possible to use JFR's Event API to collect custom events. Developers who want to collect application-level events must actively define and instantiate them in their application source code.

In this article, we'll show you how to use JMC Agent and the JMC Agent Plugin to instrument your application classes with event-emitting code. When you use JMC Agent with the JDK Flight Recorder Event API, you do not need to shut down the JVM and recompile the application code.

Note: We include an overview of the JDK Flight Recorder toolchain in this article, but we'll focus on introducing JMC Agent and the JMC Agent Plugin. To learn more about JDK Flight Recorder, see our previous article, Get started with JDK Flight Recorder in OpenJDK 8u.

Overview of the JDK Flight Recorder toolchain

JMC Agent is a subcomponent of the JDK Mission Control (JMC) project. JDK Mission Control focuses on interacting with JDK Flight Recorder and analyzing the recordings that it dumps. Although we mainly cover JMC Agent and the JMC Agent Plugin, an overview of the toolchain will be helpful.

JDK Flight Recorder

JDK Flight Recorder was originally a JRockit JVM feature. It captures key JVM statistics events and records them in a flight recording file for offline analysis. In 2018, Oracle open-sourced this feature under the name JDK Flight Recorder and integrated it into OpenJDK 11. JFR was also backported to OpenJDK 8.

Developers use JDK Flight Recorder to profile JVMs in production environments. JFR records events in compact binary forms. At a glance, it stores these events first in an in-memory buffer. Periodically, it flushes the buffer to the file system or another store. JFR is meant to be an "alway on" production time profiler and has an overhead of less than 2% for most use cases.

JDK Mission Control

Having a flight recording in a binary format is not useful by itself. OpenJDK provides tooling to access these recordings and decode their content. However, JDK Mission Control is the most powerful and flexible way to study flight recordings.

JDK Mission Control is a GUI tool specifically designed to analyze JFR recordings. Together with JDK Flight Recorder, JMC gives developers an in-depth view of the application's runtime characteristics. You can use this view to identify performance bottlenecks and fine-tune the JVM. JMC also has the ability to connect to a locally running JVM, to take new recordings, and to control ongoing recordings.

JMC is an Eclipse Rich Client Platform application. It can be extended with various plugins. It also offers a powerful standalone API for reading and analyzing JFR recordings independently from the GUI application.

Note: JDK Mission Control is available in Fedora and Red Hat Enterprise Linux (RHEL) 7 via Red Hat Software Collections (RHSCL), in RHEL 8 via the modules, and for Windows users from the OpenJDK developer portal. You can also obtain JDK Mission Control via a downstream distribution like AdoptOpenJDK.

JMC Agent

JMC Agent is a subproject of JMC. You can use this bytecode transformer to add JFR instrumentation declaratively to running applications. When you use JMC Agent, you do not need to program the JFR instrumentation in the source code.

Because the agent operates in the same address space as the JVM, it is extremely versatile. You can load it at the start of the application or sideload it dynamically at any time. A running JMC agent exposes an MBean controller for further configuration via Remote Java Management Extensions (JMX).

The JMC Agent Plugin

The JMC Agent Plugin is an ongoing effort that integrates JMC Agent features into JDK Mission Control. It is currently being developed as an external plugin in a separate project. Eventually, it will be rehomed as a submodule of JMC. Developers can use the JMC Agent Plugin to manage the JMC Agent lifecycle and control an agent's behaviors.

While the JMC Agent Plugin is in an early development stage, essential features are already usable. In the following sections, you will learn more about JMC Agent and the JMC Agent Plugin, including instructions for building an agent plugin that is usable in JDK Mission Control.

Introduction to JMC Agent

The most common way to produce events and commit them to JDK Flight Recorder is to use the standard jdk.jfr.* API, which was introduced in JDK 9. Developers who wish to use the jdk.jfr.* API to create events and operate on their instances must manually extend from a jdk.jfr.Event. An alternative, available from JMC Agent, is to use declarative and hot-swappable configurations. JDK Flight Recorder treats all events the same way, regardless of whether JMC Agent or the standard API produces them.

Creating and operating on event instances with JMC Agent

JMC Agent uses the Java Instrumentation API to register a ClassFileTransformer immediately after starting. When a class loader initiates class loading, the agent ClassFileTransformer transforms the class bytecode by injecting event-committing bytecode, which is generated according to the configuration supplied.

It's also possible to revert or update instrumentation by retransforming and reloading the class, as long as the agent can obtain a reference to target classes.

We prioritized the following requirements when we designed JMC Agent:

  • Declarative: You can use XML configuration to define inject events.
  • Unobtrusive: You do not need to change the source code to implement JMC Agent.
  • Minimal footprint: You do not need to use reflection; you only issue event-related function calls.
  • Painless: You never have to shut down the JVM.

(see below).
Together, these features help to ensure that JMC Agent is safe to use and free of side-effects. It is perfect for production use when the source is not available, or it is not possible to shut down services or maintenance immediately. JMC Agent is especially applicable for situations that require creating custom instrumentation for the application.

Next, we'll look at how to use JMC Agent and the JMC Agent Plugin.

Configuring the information to be recorded in events

JMC Agent instrumentation applies to one or more functions. You can enable or disable the following information in the XML configuration:

  • Function runtime characteristics: Start time, end time, and duration.
  • Function input and output: Parameters, return values, and exceptions (if any).
  • Event metadata: Label, description, thread ID, and call stack.

For expression evaluation, you can also use JMC Agent to enable or disable a limited subset of primary expressions. JMC Agent accepts all Java primary expressions except those containing method invocations, array accesses, or array or instance creation. This design is intended to enforce evaluation safety by static checking and to guarantee a constant-time overhead.

The agent configuration file

An agent configuration file is an XML representation controlling how a JMC Agent instruments application bytecode:

  • The document root is a <jfragent> element. As of current implementation, JMC Agent requires no namespace. A <jfragent> element has no inner text and optionally encloses the <config> and <events> elements.
  • The <config> element contains global options to be applied across all events. Currently, the options are <classprefix>, <allowconverter>, and <allowtostring>.
  • The <events> elements enclose any number of <event> elements, often called probes. Each <event> element contributes to a specific instrumentation point identified by <class>, <method>, and the method <descriptor>.
  • The <parameter> and <field> elements record the values of the given parameters and expression accordingly.

Note: See the JMC Agent repository for an example that demonstrates common usages of the Java Fight Recorder Template.

Limitations of JMC Agent

JMC Agent is in an early stage of development. At the time of this writing, we are seeking to resolve the following constraints:

  • JMC Agent is unable to work with synthetic classes.
  • It is also unable to access nestmates' private fields.
  • Newly uploaded configurations only work with classes defined with SystemClassLoader.
  • JMC Agent currently uses jdk.internal.misc.Unsafe,so you must open the module via --add-opens (see below).
  • JMC Agent is currently not integrated with JDK Mission Control.

Note: Instrumenting synthetic classes is arguably a non-goal.  It is often hard to determine the exact names of the generated classes, making them hard to work with. In most cases, it does not make sense to instrument generated code.

Introduction to the JMC Agent Plugin

Thankfully, the JMC team developed JMC on the extensible and modular Eclipse Rich Client Platform. Therefore, instead of creating new client software, the JMC Agent Plugin adds agent-related functionality to JDK Mission Control.

In a nutshell, the JMC Agent Plugin is to JMC Agent what JDK Mission Control is to JDK Flight Recorder. That is, it controls JMC agents. While still under active development, the JMC Agent Plugin already helps navigate the JMC Agent lifecycle. You can use the plugin to start JMC Agent in a local JVM and then connect to it via the JMX API. Once connected, you can apply new or modified configurations. The JMC Agent Plugin also displays live information about the resulting transformations. You can also manage predefined configurations with the preset manager and create or edit them with a set of wizards.

Live configurations

The Live Configuration page was one of the first features that we added to the JMC Agent Plugin. You can use it to see the configuration currently applied to a JMC agent in a tabular format. As shown in Figure 1, the Live Config screen's left side lists all of the events instrumented by your JMC Agent instance. The right side contains each event's definition.

The Live Config screen shows all of the events instrumented by a JMC Agent instance and each event's definition.
Figure 1. JMC Agent Plugin Live Configuration Page
Figure 1: The JMC Agent Plugin's Live Configuration page.

Note: We will eventually support saving live configurations directly into the preset manager so that you can use them later.

Editing wizards and the preset manager

Editing an XML configuration file is difficult for beginners and tedious for experienced developers, as well as being error-prone. JMC Agent Plugin introduces editing wizards to save you the hassle. Together with the preset manager, editing wizards streamline creating, modifying, and managing local configuration templates, also known as presets. Presets are especially useful when you don't need all of the configuration or event options that creating a full XML configuration would entail. Figure 2 shows the JMC Agent Plugin's preset editing wizards in the preset manager screen.

The preset manager includes editing wizards for creating, modifying, and managing local configuration templates.
Figure 2. JMC Agent Plugin Preset Manager and Preset Editing Wizards
Figure 2: Use the present manager's editing wizards to create, modify, and manage event configurations.

As an alternative to editing presets with wizards, you can use the built-in XML source editor to edit raw configuration files manually. While we don't have them yet, we'll eventually add inline error and warning indications to the XML source editor.

You've been introduced to JMC Agent and the JMC Agent Plugin. We'll conclude with quick instructions for building and running these tools in your applications. Be sure to check out the demonstration video at the end of the article, as well.

Build and run JMC Agent

To build and run JMC Agent, you will need JDK 7 or later. To start, clone the JMC source tree from its GitHub repository:

$ git clone https://github.com/openjdk/jmc.git

Then, use Maven in the agent folder:

$ mvn clean package

After the build succeeds, you will find the agent JAR under the target directory:

$ ls target/org.openjdk.jmc.agent-*.jar

To run your application with the agent statically attached, use the -javaagent option to specify the agent's JAR path and optional configuration path:

$ java -XX:+FlightRecorde  -javaagent:<path-to-agent-jar>[=<path-to-agent-config>]  <rest-of-your-app-cmd>

Export the Unsafe class in JDK 9 and above

When using JDK 9 and above, remember to export the Unsafe class with the --add-opens option for the agent to use:

--add-opens java.base/jdk.internal.misc=ALL-UNNAMED

For instance, to run the sample program that is included with JMC Agent, you would enter:

$ java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED -XX:+FlightRecorder -javaagent:target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar=target/test-classes/org/openjdk/jmc/agent/test/jfrprobes_template.xml -cp target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar:target/test-classes/ org.openjdk.jmc.agent.test.InstrumentMe

Build JMC with the JMC Agent Plugin enabled

Until the JMC Agent Plugin is packaged with JDK Mission Control, you'll have to build it yourself. Before you attempt to follow these instructions, make sure that you have JDK 8 and Maven installed on your system.

First, clone the JMC source tree, if you haven't already done so:

$ git clone https://github.com/openjdk/jmc.git

Then, clone the JMC Agent Plugin source tree:

$ git clone https://github.com/rh-jmc-team/jmc-agent-plugin.git

Copy the agent plugin and feature folds from the JMC Agent Plugin source tree to JMC:

$ cp -r jmc-agent-plugin/org.openjdk.jmc* jmc/application

Apply the patch to JMC's root directory:

$ cd jmc
$ patch -p0 < ../jmc-agent-plugin/scripts/diff.patch

Get third-party dependencies into a local p2 repository and make it available on localhost:

$ cd jmc/third-party && mvn p2:site && mvn jetty:run

Finally, in another terminal, compile and package JMC:

$ cd jmc/core && mvn install && cd .. && mvn package

Do more with JMC Agent: Video presentation and demo

This article is based on an internal presentation that we made to the Red Hat JDK Mission Control team, now available for public viewing.

The demonstration starts at 8:50 minutes and was recently updated to reflect the latest development progress. You will find the demonstration code on the GitHub repository for the Dining philosophers demo.

Conclusion

JDK Flight Recorder is meant to be a production-time profiler. In this context, JMC Agent is a powerful addition to the JFR toolchain. As a developer, JMC Agent lets you instrument your runtime applications without needing to shut down the JVM. You do not need to refactor and recompile your code to redeploy the application.

There are, of course, alternatives to using JMC Agent. For example, Byteman is a powerful tool that you can configure to inject similar instrumentation into a running application. However, JMC Agent's dedication to JFR event instrumentation makes it more suitable and easier to use for problems that require custom JFR events. Although it is a work in progress, adding the JMC Agent Plugin to JDK Mission Control further elevates the ease-of-use factor in this toolchain.

Both JMC Agent and the JMC Agent Plugin are currently under development, with new features being planned and implemented constantly. If you would like to contribute to this project or have ideas, suggestions, or feedback, you can join the discussion on the JMC-dev mailing list.

Last updated: March 14, 2024