Overview
JDK Flight Recorder (JFR) is an event-recording engine built into HotSpot: hundreds of typed
events — GC phases, allocation, method samples, lock contention, I/O, class loading, safepoints
— are collected into ring buffers with overhead low enough (targeted ~1% with default settings)
to leave on in production. Mission Control (JMC) is the companion desktop application that
analyses the resulting .jfr recordings. Both were commercial Oracle features until open-sourced
in JDK 11 (JFR, JEP 328) and as the separate JMC project; together they are the "black box"
flight recorder metaphor made literal — when something goes wrong, the data was already being
recorded.
Key points
- Low overhead by design: events are written to thread-local buffers, flushed to an in-memory ring or disk repository in a binary, self-describing format; sampling-based method profiling avoids safepoint bias better than JVMTI-based profilers of old.
- Two standard settings sets:
default(~<1%, always-on safe) andprofile(~2%, adds allocation sites and finer sampling) —.jfcfiles you can copy and customise (JMC has a template editor). - Starting at launch:
-XX:StartFlightRecording=duration=120s,settings=profile, filename=app.jfr; or continuous:maxage=12h,maxsize=500m,name=mainwith no filename, then dump on demand. - Controlling live:
jcmd <pid> JFR.start/JFR.dump/JFR.check/JFR.stop— dump the last N minutes from a continuous recording after an incident:jcmd 4242 JFR.dump name=main maxage=10m filename=incident.jfr. jfrCLI tool:jfr print,jfr summary,jfr view <view> file.jfr(Java 21 added useful built-in views likehot-methods,gc-pauses) for headless triage without JMC.- Custom events: the
jdk.jfrAPI — subclassjdk.jfr.Event, annotate,commit()— puts your business/framework events on the same timeline as JVM events; frameworks lean on this (Spring'sApplicationStartuphas aFlightRecorderApplicationStartupvariant, Quarkus and Micronaut publish framework events similarly). - Streaming (JEP 349, Java 14):
RecordingStreamsubscribes to events in-process (or from the disk repository of another process) as they happen — the bridge from JFR to live metrics dashboards without waiting for a dump. - JMC analysis: automated-analysis page (rule engine flags problems in plain language),
method profiling flame graph, allocation by site, lock contention ("Java Blocking"), GC pause
breakdowns, memory-leak hunting via
OldObjectSamplereference chains. - Not in the JDK: JMC downloads separately (Oracle, Adoptium/Eclipse builds); JFR itself needs nothing installed — every JDK 11+ (and 8u262+ backport) has it.
Details
Recording lifecycle in practice
| Situation | Move |
|---|---|
| Always-on production insurance | -XX:StartFlightRecording=maxage=12h,maxsize=1g,name=main at launch |
| Incident just happened | jcmd <pid> JFR.dump name=main maxage=15m filename=inc.jfr |
| Reproduce a slow endpoint | jcmd <pid> JFR.start settings=profile duration=2m filename=slow.jfr |
| Headless first look | jfr summary inc.jfr, jfr view hot-methods inc.jfr |
| Real analysis | open in JMC → Automated Analysis → drill into Method Profiling / GC / Locks |
JFR vs async-profiler
- async-profiler samples via perf/itimer signals and reads native + Java stacks, so it sees kernel/native frames JFR's Java-oriented sampler misses, and produces flame graphs directly; it can also profile allocations and locks, and can even emit JFR-format output.
- JFR wins on breadth (GC, I/O, safepoints, class loading, custom events on one timeline), on being pre-installed and supported everywhere, and on continuous always-on recording.
- Sensible split: JFR as the always-on baseline and incident recorder; async-profiler when you need precise CPU flame graphs including native code (e.g. chasing the last percent in a 1BRC-style optimisation). Since JDK 17+ JFR's own CPU sampling has improved, and JMC renders flame graphs too, so the gap narrows.
Reading a recording — where to look first
- Automated Analysis — triage, often names the problem outright.
- Java Application → Method Profiling — CPU hot spots (check sample count is meaningful).
- Memory → Allocation — allocation pressure by site; feeds GC tuning.
- Java Application → Locks — monitor contention with stack traces.
- JVM Internals → Garbage Collections / Safepoints — pause culprits that are not GC.
Examples
# continuous recording + post-incident dump
java -XX:StartFlightRecording=name=main,maxage=12h,maxsize=1g -jar app.jar
jcmd $(pgrep -f app.jar) JFR.dump name=main maxage=10m filename=/tmp/incident.jfr
jfr view hot-methods /tmp/incident.jfr
@Name("shop.OrderPlaced")
@Label("Order placed")
class OrderPlaced extends jdk.jfr.Event {
@Label("Order id") long orderId;
@Label("Total cents") long totalCents;
}
// ... new OrderPlaced() ... event.commit();
// streaming: live consumption in-process
try (var rs = new jdk.jfr.consumer.RecordingStream()) {
rs.enable("jdk.GCPhasePause");
rs.onEvent("jdk.GCPhasePause", e ->
System.out.println(e.getDuration().toMillis() + " ms pause"));
rs.startAsync();
}
Related
- JDK & tools — parent catalogue of the JDK toolchain.
- jcmd — the runtime remote-control for recordings (
JFR.*). - jconsole — live JMX values vs JFR's recorded events.
- jstat — the zero-setup counter view JFR largely supersedes.
- jmap — heap dumps when
OldObjectSampleis not enough. - One Billion Row Challenge — the kind of optimisation work where JFR/async-profiler earn their keep.
- Performance engineering — profiling as part of a disciplined performance method.