Core Java

JFR Event Streaming: Continuous Production Profiling Without a Vendor Agent

Your JVM already ships with a profiler that is safe for production. Since JDK 14, it can also talk to you in real time.

Why You May Not Need an Agent

Many teams reach for a vendor agent the moment production slows down. However, the JDK has carried its own recorder for years. JEP 328 brought Flight Recorder into OpenJDK 11, according to Oracle’s Java Magazine. Until Java 14, though, it only produced files that you had to dump and open later.

That changed with JEP 349. It exposes JFR data for continuous monitoring, and the JEP notes that HotSpot emits more than 500 data points through JFR. As a result, you can react to events while the application is still running.

How Event Streaming Works

Inside the Same JVM

The entry point is RecordingStream in the jdk.jfr.consumer package. You enable the events you care about, register a handler, and start the stream. Each handler is simply a lambda that runs when an event arrives.

From Another Process

Streaming also works from outside. JEP 349 explains that the disk repository location is stored in the jdk.jfr.repository system property, which you can read through the attach API. In addition, RemoteRecordingStream in the jdk.management.jfr module lets a separate process consume events, as the Inside Java overview shows.

A Small Example You Can Run

The program below needs JDK 14 or newer (I ran it on JDK 21). Save it as Stream.java, then start it with the single command underneath. It prints CPU load every second and reports any lock wait longer than 10 ms.

import java.time.Duration;
import jdk.jfr.consumer.RecordingStream;

public class Stream {
    public static void main(String[] args) throws Exception {
        try (var rs = new RecordingStream()) {
            rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
            rs.enable("jdk.JavaMonitorEnter")
              .withThreshold(Duration.ofMillis(10));
            rs.onEvent("jdk.CPULoad", e -> System.out.printf(
                "jvm user cpu: %.1f%%%n", e.getFloat("jvmUser") * 100));
            rs.onEvent("jdk.JavaMonitorEnter", e -> System.out.println(
                "lock wait: " + e.getDuration().toMillis() + " ms"));
            rs.startAsync();

            Object lock = new Object();
            Thread t = new Thread(() -> {
                synchronized (lock) {
                    try { Thread.sleep(300); } catch (Exception x) { }
                }
            });
            t.start();
            Thread.sleep(50);
            synchronized (lock) { }
            Thread.sleep(3000);
        }
    }
}
java Stream.java

On my machine it printed a lock wait of roughly 250 ms, followed by one CPU line per second. Your exact numbers will differ, naturally.

What Does It Cost?

Overhead is the question every operator asks first. The JDK answers it inside its own configuration files. The default setting is described as safe for continuous production use, typically under 1% overhead, while profile is described as typically around 2%. Notice that these are documented targets, not measurements from my workloads, so test your own service before trusting them.

SettingDocumented overheadIntended use
defaultTypically under 1%Always-on production recording
profileTypically around 2%Short, focused investigations

The difference comes from how aggressively each setting collects data. I read both files from the JDK 21 installation on my machine. The profile setting simply halves every threshold, so it catches shorter waits and slower calls.

Similarly, profile samples twice as often. Converting the configured values into samples per second makes this easy to see.

One more caution applies here. According to the Inside Java overview, how you implement a stream can itself affect performance. Therefore, keep handlers short, and push heavy work onto another thread.

A Practical Production Setup

First, start the JVM with a rolling on-disk recording. Replace app.jar with your own application.

java -XX:StartFlightRecording:name=prod,settings=default,maxage=6h,disk=true -jar app.jar

Later, if something looks wrong, copy the recent data to a file with jcmd. The JVM prints this hint itself at startup. Replace <pid> with your process id.

jcmd <pid> JFR.dump name=prod filename=/tmp/prod.jfr

Quick checklist

  • Start with the default setting, never profile, for always-on use.
  • Enable only the events you actually read.
  • Keep stream handlers fast and non-blocking.
  • Set maxage so the disk repository stays bounded.
  • Measure overhead on your own workload first.

Streaming Versus Dump Files

QuestionEvent streamingDump file
When do you see data?As events arriveAfter you dump
Best forAlerts and live metricsDeep post-incident analysis
Extra toolingA small consumer programA viewer such as JDK Mission Control

To be fair, a vendor product still adds dashboards, long-term storage, and cross-service views that JFR does not provide on its own. Nevertheless, for many teams the built-in route covers the first and most urgent questions at no licence cost.

What We Have Learned

JFR has been part of OpenJDK since version 11, and JEP 349 added live event streaming in Java 14. A RecordingStream lets one JVM watch itself, while a remote stream and the disk repository allow outside consumers. The documented overhead is typically under 1% for the default setting and around 2% for profile, and profile simply samples twice as aggressively. Consequently, a rolling default recording plus a small stream consumer gives you continuous production insight without installing a vendor agent. Just measure on your own system, and keep your handlers light.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button