Java 14 JFR Event Streaming Tutorial with Examples
Introduction
Java Flight Recorder (JFR) is a monitoring tool built into the JVM. It records events - garbage collections, thread locks, CPU load, memory allocation, exceptions, file and socket I/O, and events defined by the application - with an overhead low enough to keep it on in production. Until Java 14, though, JFR data could only be analyzed after a recording was written to a file, typically with JDK Mission Control. Java 14 (JEP 349) added JFR event streaming: a program can subscribe to events while they are being recorded, and react to them within about a second. This tutorial explains JFR event streaming with examples. We will start a recording stream, subscribe to garbage collection events, define a custom event, and read events back from a recording file.
(Note - This feature is part of Java 14Click to read tutorial: Overview of New Features in Java 14. JFR itself was open-sourced in Java 11Click to read: Java 11.) The streaming API The API lives in the
Streaming built-in and custom events
The program below streams two kinds of events: the JVM's built-in
OUTPUT of the above code
Explanation of the code
Reading a recording file
JFR recordings are usually saved to
OUTPUT of the above code
The same file could be opened in JDK Mission Control, or summarized on the command line with the
(Note - This feature is part of Java 14Click to read tutorial: Overview of New Features in Java 14. JFR itself was open-sourced in Java 11Click to read: Java 11.) The streaming API The API lives in the
jdk.jfr.consumer package of the jdk.jfr module -
RecordingStream- starts a new recording in the current JVM and streams its events. You choose events withenable(eventName), optionally with settings such aswithPeriod()orwithThreshold().onEvent(name, action)- registers a callback, aConsumer<RecordedEvent>, for one type of event.RecordedEventgives access to the event's fields by name, its start time and its duration.start()runs the stream on the current thread until it is closed;startAsync()runs it in the background.EventStream.openRepository()streams events from another JVM's recording on the same machine, andEventStream.openFile()reads a recording file.
jdk.GarbageCollection event, and a custom OrderEvent defined by the application. A custom event is a class which extends jdk.jfr.Event; its fields become the event's fields.
Subscribing to JFR events with RecordingStream
//JfrStreamingDemo.java
import java.util.concurrent.atomic.AtomicInteger;
import jdk.jfr.Event;
import jdk.jfr.Label;
import jdk.jfr.Name;
import jdk.jfr.consumer.RecordingStream;
public class JfrStreamingDemo {
@Name("com.javabrahman.Order")
@Label("Order Placed")
static class OrderEvent extends Event {
@Label("Item") String item;
@Label("Amount") double amount;
}
public static void main(String[] args) throws Exception {
AtomicInteger gcCount = new AtomicInteger();
try (RecordingStream stream = new RecordingStream()) {
stream.enable("jdk.GarbageCollection");
stream.enable("com.javabrahman.Order");
stream.onEvent("com.javabrahman.Order", e -> System.out.printf(
"Order event: %s for %.2f (took at least 20 ms: %b)%n",
e.getString("item"), e.getDouble("amount"), e.getDuration().toMillis() >= 20));
stream.onEvent("jdk.GarbageCollection", e -> {
if (gcCount.incrementAndGet() == 1)
System.out.println("GC event: " + e.getString("name") + ", cause: " + e.getString("cause"));
});
stream.startAsync(); // stream in the background
for (String item : new String[] {"Book", "Pen"}) {
OrderEvent event = new OrderEvent();
event.begin(); // start timing the event
Thread.sleep(20); // stands in for real work
event.item = item;
event.amount = item.length() * 10.5;
event.commit(); // record it
}
System.gc(); // trigger a garbage collection event
Thread.sleep(2500); // give the stream time to deliver the events
}
System.out.println("Saw GC events: " + (gcCount.get() > 0));
}
}
Order event: Book for 42.00 (took at least 20 ms: true) Order event: Pen for 31.50 (took at least 20 ms: true) GC event: G1Full, cause: System.gc() Saw GC events: true
new RecordingStream()starts a recording in the same JVM. Only the enabled events are recorded, which keeps the overhead low.OrderEventis an ordinary class with JFR annotations:@Namegives the event's unique name, used inenable()andonEvent(), and@Labelgives readable names shown in tools like JDK Mission Control.begin()andcommit()mark the start and end of the event, so JFR records its duration as well as its fields. If the event type is not enabled,commit()does almost nothing, so instrumented code costs little when nobody is listening.System.gc()causes a full collection, reported by the default G1 collector asG1Fullwith the causeSystem.gc().- Events are written to an in-memory buffer and flushed about once a second, so they reach the callbacks with a short delay. That is why the program waits before closing the stream.
.jfr files, either from code with the Recording class, or by starting the JVM with -XX:StartFlightRecording. The consumer API can read them back -
Recording to a file and reading it back
//JfrFileDemo.java
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.TreeMap;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingFile;
public class JfrFileDemo {
public static void main(String[] args) throws Exception {
Path file = Files.createTempFile("app", ".jfr");
try (Recording recording = new Recording()) {
recording.enable("jdk.GarbageCollection");
recording.enable("jdk.ThreadSleep");
recording.start();
System.gc();
Thread.sleep(50);
recording.stop();
recording.dump(file);
}
Map<String, Integer> counts = new TreeMap<>();
for (RecordedEvent event : RecordingFile.readAllEvents(file)) {
counts.merge(event.getEventType().getName(), 1, Integer::sum);
}
System.out.println("Event types in the file: " + counts.keySet());
Files.delete(file);
}
}
Event types in the file: [jdk.GarbageCollection, jdk.ThreadSleep]
jfr tool, for example jfr summary app.jfr or jfr print --events jdk.GarbageCollection app.jfr.
What event streaming is used for
- Continuous monitoring: pushing selected JFR metrics, such as GC pause times or lock contention, to a monitoring system like Prometheus, without an external agent.
- Alerting: reacting immediately when an event crosses a threshold, for example logging a warning when a GC pause exceeds 100 ms.
- Application metrics: custom events like
OrderEventgive business-level timings with very low overhead, and appear alongside the JVM's own events.
RecordingStream to enable events and register onEvent() callbacks, received a built-in garbage collection event, and defined and timed a custom event by extending jdk.jfr.Event. We then saved a recording to a file and read it back with RecordingFile, and lastly looked at what event streaming is used for. Next, read the overview of new features in Java 15Click to read tutorial: Overview of New Features in Java 15.Continue Reading Java 12 to 17 Tutorials
Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14 Java Switch Expressions: Arrow Labels and yieldClick to read tutorial: Java Switch Expressions: Arrow Labels and yield Java 14 Helpful NullPointerExceptions ExplainedClick to read tutorial: Java 14 Helpful NullPointerExceptions Explained Overview of New Features in Java 15Click to read tutorial: Overview of New Features in Java 15 Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Overview of New Features in Java 17Click to read tutorial: Overview of New Features in Java 17 Java 12 API Additions: String.indent, teeing, mismatchClick to read tutorial: Java 12 API Additions: String.indent, teeing, mismatch
Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14 Java Switch Expressions: Arrow Labels and yieldClick to read tutorial: Java Switch Expressions: Arrow Labels and yield Java 14 Helpful NullPointerExceptions ExplainedClick to read tutorial: Java 14 Helpful NullPointerExceptions Explained Overview of New Features in Java 15Click to read tutorial: Overview of New Features in Java 15 Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Overview of New Features in Java 17Click to read tutorial: Overview of New Features in Java 17 Java 12 API Additions: String.indent, teeing, mismatchClick to read tutorial: Java 12 API Additions: String.indent, teeing, mismatch