JFR Event Stream - Sip of Java

JDK Flight Recorder (JFR) is an event-based tool for monitoring and profiling built into the JDK. JFR has a very low overhead of <1% using default settings allowing it to be used in production. Typically extracting information out of JFR requires performing a dump; however, added in JDK 14, the package jdk.jfr.consumer provides APIs for the consumption of JFR events without requiring a JFR dump to be done. Let’s take a look at how this API can be used.

Creating JFR Event Streams

The central class in the new Event Streaming API is the interface jdk.jfr.consumer.EventStream, which, as the name suggests, represents a stream of events.

EventStream Factory Methods

EventStream provides three factory methods for creating a new EventStream instance:

  • openFile(java.nio.file.Path) - Creates an EventStream from a recording file.
  • openRepository() - Creates an EventStream from the repository of the current JVM.
  • openRepository(java.nio.file.Path) - Creates an EventStream from a directory.

EventStream Implementations

There are also two public implementations which can be instantiated as well:

  • jdk.jfr.consumer.RecordingStream - A recording stream of the current JVM.
  • jdk.management.jfr.RemoteRecordingStream - A recording stream that can serialize events using a javax.management.MBeanServerConnection

Registering Events

Once an EventStream has been created, actions can be registered to it. There are two ways of registering actions for events:

  • onEvent(consumer) - all events will be registered to this action.

  • onEvent(String, Consumer) - only events whose name matches will be registered to this action.

Additionally actions can be registered for when other conditions are met:

  • onClose(Runnable) - Registers an action to perform when the stream is closed.
  • onError(Consumer<Throwable>) - Registers an action to perform if an exception occurs.
  • onFlush(Runnable) - Registers an action to perform after the stream has been flushed.
  • onMetadata(Consumer<MetadataEvent>) - Registers an action to perform when new metadata arrives in the stream.

    Example

An example of creating a JFR event stream might look like this. In this example, the stream checks for when a monitored process is reading from a file and prints to console the event’s start time, end time, and total duration.

try (EventStream es = EventStream.openRepository()) {
    es.onEvent("jdk.FileRead", event -> {
     	System.out.println("File read!");
        System.out.println("Start: " + event.getStartTime());
        System.out.println("End: " + event.getEndTime());
        System.out.println("Duration: " + event.getDuration());
    });
    es.start();
}

Attaching Streams

A JFR event stream can be attached as both an in-process and an external process. Examples of how a JFR event stream can be attached to an external process include:

  • Java Agent
  • Process Id
  • JMX Socket
  • Read from a recording file
  • And more!

JFR Event Streams Performance Overhead

While JFR targets a <1% overhead with default settings, how a JFR Event Stream is implemented can impact performance. How many events are registered to the stream and the action(s) behavior will determine the performance impact for an in-process stream.

For external streams, how events are retrieved might impact performance. If events are streamed over a JMX socket, that will require the stream events to be serialized, which could mean significant overhead. However, if the external stream is reading from a recording file or directory, this will not put any additional overhead on the monitored process.

Additional Reading

Happy coding!