// JFR Event Streaming with Java 14
The Java Flight Recorder has a long history. It used to be part of the BEA JRockit JVM, became a commercial feature of the Oracle JDK (7+) after Oracle acquired BEA and Sun and was finally fully open sourced with the release of OpenJDK 11 (backports exist for 8) (JEP328). OpenJDK 14 will add some improvements to the JFR.
JEP 349 will allow 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.
JEP 349 made it already into the early access builds and can be experimented with since OpenJDK 14 build 22+. Lets check it out.
In-Process Streaming
The base JFR configuration files (XML) can be found in JDK_HOME/lib/jfr. The default configuration (default.jfc) is relatively low overhead, while profile.jfc will provide more data. Java Mission Control can create custom settings based on templates if needed. I am using the default config for the examples.
The first example will start JFR on the local JVM using the default recorder settings and register a few event handlers to check if it is working.
import java.io.IOException;
import java.text.ParseException;
import jdk.jfr.Configuration;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.consumer.RecordingStream;
public class JFRStreamTest {
public static void main(String[] args) throws IOException, ParseException {
Configuration config = Configuration.getConfiguration("default");
System.out.println(config.getDescription());
System.out.println("settings:");
config.getSettings().forEach((key, value) -> System.out.println(key+": "+value));
// open a stream and start local recording
try (EventStream es = new RecordingStream(config)) {
// register event handlers
es.onEvent("jdk.GarbageCollection", System.out::println);
es.onEvent("jdk.CPULoad", System.out::println);
es.onEvent("jdk.JVMInformation", System.out::println);
// start and block
es.start();
}
}
}
The above example should print information about the running JVM once, CPU load periodically and GC events when they occur.
Out-of-Process Streaming
Simply start the flight recorder as usual via jcmd <PID> JFR.start or via the JVM flag -XX:+FlightRecorder at startup. The repository location will be stored in the jdk.jfr.repository system property as soon JFR is running (new in Java 14). It can also be set at startup via a comma separated list of flight recorder options: -XX:FlightRecorderOptions:repository=./blackbox
Update thanks to Erik from Oracle in the comments section: The repository location can be also set using jcmd <PID> JFR.configure repositorypath=<directory>. If you set it after a recording has started, new data will be written in the new location.
$ jcmd -l | grep netbeans
8492 org.netbeans.Main ... --branding nb
$ jcmd 8492 JFR.start name=streamtest
Started recording 1. No limit specified, using maxsize=250MB as default.
Use jcmd 8492 JFR.dump name=streamtest filename=FILEPATH to copy recording data to file.
$ jinfo -sysprops 8492 | grep jfr
jdk.jfr.repository=/tmp/2019_11_18_02_19_59_8492
Now that the recording is running and we know where the repository is, a second JVM can open a stream to the live JFR repository and monitor the application. Note that we did not dump any JFR records to a file, we connect to the live repository directly.
import java.io.IOException;
import java.nio.file.Path;
import jdk.jfr.consumer.EventStream;
public class JFRStreamTest {
public static void main(String[] args) throws IOException {
// connect to JFR repository
try (EventStream es = EventStream.openRepository(Path.of("/tmp/2019_11_18_02_19_59_8492"))) {
// register some event handlers
//es.onEvent("jdk.CPULoad", System.out::println);
es.onEvent("jdk.SocketRead", System.out::println);
es.onEvent("jdk.SocketWrite", System.out::println);
// start and block
es.start();
}
}
}
As a quick test i monitored with the example above a NetBeans instance running on Java 14 and let the IDE check for updates. Since we watch for SocketRead and Write events the output looked like:
jdk.SocketRead { startTime = 04:34:09.571 duration = 117.764 ms host = "netbeans.apache.org" address = "40.79.78.1" port = 443 timeout = 30.000 s bytesRead = 5 bytes endOfStream = false eventThread = "pool-5-thread-1" (javaThreadId = 163) stackTrace = [ java.net.Socket$SocketInputStream.read(byte[], int, int) line: 68 sun.security.ssl.SSLSocketInputRecord.read(InputStream, byte[], int, int) line: 457 sun.security.ssl.SSLSocketInputRecord.decode(ByteBuffer[], int, int) line: 165 sun.security.ssl.SSLTransport.decode(TransportContext, ByteBuffer[], int, int, ... sun.security.ssl.SSLSocketImpl.decode(ByteBuffer) line: 1460 ... ] } ...
Streaming Dumped Records
Opening streams (EventStream.openFile(path)) to JFR record dumps (jcmd <PID> JFR.dump filename=foo.jfr) is also possible of course - works as expected.
Conclusion
Pretty cool new feature! It is currently not possible to do in-memory but out-of-process steaming without syncing on repository files. But since a ramdisk can workaround this issue so easily I am not even sure if this capability would be worth it.
fly safe
Posted by Simon Goller on November 21, 2019 at 07:57 PM CET #
Posted by Erik on December 11, 2019 at 02:48 PM CET #
Posted by Michael Bien on December 11, 2019 at 11:59 PM CET #