With the recent JDK Mission Control (JMC) 8.1 release, now is a good time to look at the new JMC agent plugin. The plugin, which was merged upstream, provides a convenient way to add custom JDK Flight Recorder (JFR) events to a running Java virtual machine (JVM) without restarting or rebuilding the Java application.
JDK Mission Control and JDK Flight Recorder
Monitoring Java application performance in a production environment can be costly, even more so in environments where restarting the JVM to install monitoring capabilities is unfeasible or undesirable. JDK Flight Recorder (JFR) is a Java application monitoring solution built into the JVM that offers minimal performance overhead and setup.
Note: Do you need to be able to retrieve, store, and analyze flight recordings from containerized JVMs? Check out Cryostat 2.0—JDK Flight Recorder for containers.
JMC agent
JMC agent is a tool being developed alongside JDK Mission Control to inject custom JDK Flight Recorder events at runtime without needing to restart the JVM. The agent works by injecting bytecode for custom JDK Flight Recorder events, as well as the bytecode to emit them within specified methods. JMC agent is also able to capture the values of method parameters and class fields and emit them as part of custom events. JDK Mission Control 8.1 includes a plugin for controlling the agent throughout its lifecycle.
Limitations of JMC agent
JMC agent has a few limitations that developers should be aware of:
- It doesn't work with synthetic classes.
- It is also unable to access nestmates' private fields.
- Newly uploaded configurations work only with classes defined with the system class loader.
How to get the JMC agent plugin
Because the JMC agent plugin is now part of upstream JDK Mission Control 8.1, you may simply download and run it to start using it. The agent plugin requires the JMC agent JAR, as well. You can download the JDK Mission Control 8.1.0 and JMC agent JAR from the jmc-overrides GitHub page of the Eclipse Adoptium Working Group.
Using the JMC agent plugin
To illustrate the use of the JMC agent plugin with an example, consider a program that reads words from a webpage, does analysis on them, and stores them in a database. A couple of sample methods follow:
private static void loadWords(String path) {
try {
List<String> lines = retrieveWords(path);
for (String line : lines) {
for (String word : Arrays.asList(line.split(" "))) {
doWork(word);
storeWord(word);
}
}
} catch (Exception e) {
e.printStackTrace();
System.err.println(e.getMessage());
}
}
private static void storeWord(String word) {
Jedis jedis = pool.getResource();
jedis.sadd(key, word);
pool.returnResource(jedis);
}
For this article, we want to instrument the storeWord
method with a custom event and capture the word passed to it as its parameter. To do this without the agent, we would need to create our own custom event class, add the code to emit the class to the function, then recompile and restart the application. In a production environment, this is costly and potentially unfeasible. Using the agent, however, we can do it with minimal intrusion.
When you start JDK Mission Control, the agent plugin will be available under the JVM browser. Simply find the JVM you wish to instrument and select the agent option, as shown in Figure 1. You will be prompted for the agent's JAR file and an optional XML file with a probe definition to load. This XML file is optional because the agent plugin has wizards to guide the creation process and remove the need to manually write the XML.
After you supply the agent JAR, you are taken to the landing page for the agent plugin, shown in Figure 2. The page displays the currently active injected events, if any, and the currently active agent configuration.
On this page, you can save the currently active probes and configurations as a preset for later use, using the save icon in the top right corner of the page. Alternatively, load a configuration via the load icon.
Configuring JMC agent in the preset manager
Another way to create presets is by using the preset manager, which is located under the window menu. This preset manager lets you not only create new presets from existing configurations, but also create new presets from a set of wizards to guide the process.
The first page that appears when you use the preset manager is the configuration options for the agent, shown in Figure 3. You can use these options to define the prefix used for any generated event classes, as well as whether the agent is allowed to call toString
methods and use converters. These are advanced functionality, enabling the capture of method parameters, return values, and fields.
Defining custom events
From here, you can begin defining the custom events you want to inject. Begin by defining the name and description of the custom event, as well as the class and method you want to instrument with it, as shown in Figure 4. Provide a fully qualified name as the class name, and the method descriptor for the method being instrumented.
The method descriptor takes the form of (Parameter Descriptor*)Return Descriptor
. For example, for the method void storeWord(String word)
, the method descriptor would be (Ljava/lang/String;)V
. See the JVM specification for more information about the method descriptor format.
Additional options control whether to record stack traces and exceptions, as well as where to inject events (such as the beginning of the method or its end). In this case, we’ll inject the event at the start.
Capturing event parameters and fields
Following this page are additional wizards for capturing parameters, and for returning the values of the instrumented methods as well as fields within the class being instrumented, as shown in Figure 5. To capture the parameter we’re interested in, we need to provide the index of the parameter and whether it is a return value. In this case, since it’s the first and only parameter, it has the index 0.
When you’re done creating events and capturing parameters and fields, the agent plugin provides a preview of the completed XML configuration, as shown in Figure 6. Once finished, the configuration can be loaded into the target JVM, and JDK Mission Control can then be used to start a flight recording with the newly created events.
Once the preset is complete and has been loaded in, the events and configuration options are displayed on the agent plugin's status page, shown in Figure 7.
Conclusion
Together, JDK Flight Recorder, JDK Mission Control, and JMC agent provide a powerful set of tools for monitoring and diagnosing JVM performance. These tools work in an unobtrusive manner with minimal performance overhead. The agent plugin further builds on these tools and provides an easy way to inject custom flight recorder events into the JVM at runtime without needing to restart it, which can be a lengthy and expensive process in a production environment.
Although there are alternatives to JMC agent, such as Byteman, they are often suited for much more general uses. The JMC agent and agent plugin are designed for creating and injecting JDK Flight Recorder events, making them better suited for these purposes. With the release of JDK Mission Control 8.1, the agent plugin is now provided out of the box, making it easier than ever to take advantage of these tools for any performance monitoring needs.
The JMC agent and agent plugin are both still in development. To keep up to date with these developments and provide ideas and feedback, feel free to join the jmc-dev mailing list and track development in the upstream GitHub repository.
Last updated: September 20, 2023