Java 14 - JFR Event Streaming (JEP 349)

Before Java 11, Java Flight Recorder (JFR) was one of the commercial features, and by default is disabled in the JVM. JEP 328 bring JFR as a common feature in Java 11.

JFR is a tuning tool for the JVM, and generally works in conjunction with JDK Mission Control (JMC). It collects various events in the JVM and java applications continuously to provide data for subsequent JMC analysis.

After JFR collects the Event from the JVM, it will write it to a small thread-local cache, then refresh it to a global memory cache, and finally write the data in the cache to the storage. The data captured in the storage, then will need to be read and parse later, for analysis — so it's not real time.

Let's jump to code! I have this FibonacciEvent that extending jdk.jfr.Event.

FibonacciEvent.java
package com.dariawan.jdk11;

import jdk.jfr.Description;
import jdk.jfr.Event;
import jdk.jfr.Label;
import jdk.jfr.Name;

@Name("Fibonacci")
@Label("Fibonacci")
@Description("Dariawan example: Fibonacci in Flight Recorder")
public class FibonacciEvent extends Event {
    @Label("Message")
    public String message;
 
    public int fibonacci(int n) {
        if (n <= 1) {
            return n;
        }
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}
                    

In JEP328JFR, the events collected is dumped to temporary file, and parsed later using RecordingFile.readAllEvents(path):

JEP328JFR.java
package com.dariawan.jdk11;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingFile;

public class JEP328JFR {

    public static void main(String args[]) throws IOException {
        File file = File.createTempFile("tmp", ".dat");
        file.deleteOnExit();
        Path path = file.toPath();
        FibonacciEvent event = new FibonacciEvent();

        try (var r = new Recording()) {
            r.enable(FibonacciEvent.class).withoutThreshold();
            r.start();
            for (int n = 1; n < 40; n++) {
                event.message = String.valueOf(event.fibonacci(n));
                event.commit();
            }
            r.stop();
            r.dump(path);
        }

        for (RecordedEvent re : RecordingFile.readAllEvents(path)) {
            System.out.println(re.getString("message"));
        }

        // java --enable-preview com.dariawan.jdk11.JEP328JFR        
    }
}
                    

As there is a need for continues monitoring, and not for later analysis, that's why there is JEP 349. This enhancement allowing the continuous consumption of java flight recorder events in-memory from within the same JVM, or out-of-process from a different JVM via its JFR repository file. All are done without the need to dump the recorded events to storage and parse it later. So now; profiling, analysis, or debugging in real time is possible using JFR.

JEP349JFR.java
package com.dariawan.jdk14;

import com.dariawan.jdk11.FibonacciEvent;
import java.time.Duration;
import jdk.jfr.consumer.RecordingStream;

public class JEP349JFR {

    public static void main(String[] args) throws InterruptedException {

        try (var rs = new RecordingStream()) {
            // rs.enable(FibonacciEvent.class);
            rs.onEvent("Fibonacci", event -> {
                System.out.println(event.getString("message"));
            });

            rs.startAsync();
            for (int n = 1; n < 40; n++) {
                FibonacciEvent event = new FibonacciEvent();
                event.message = String.valueOf(event.fibonacci(n));
                event.commit();
            }
            rs.awaitTermination(Duration.ofSeconds(30));
            // java --enable-preview com.dariawan.jdk14.JEP349JFR
        }
    }
}
                    

In JEP349JFR, the event is available in real-time, and we can access and print it immediately.