How Java Flight Recorder records JVM profiles with low overhead
See how JFR filters typed events, writes private buffers, samples stacks, reuses metadata, and preserves recent history in recording chunks.
Lesson summary
JFR stays light by filtering early, writing privately, sampling sparsely, reusing metadata, and rotating complete chunks.
- Settings reject unneeded events before their data enters the recording path.
- Private write lanes let application threads append without sharing one hot cursor.
- Periodic snapshots trade perfect detail for a useful profile with bounded work.
- Shared IDs keep repeated stacks and methods from bloating every event record.
- Self-contained chunks turn compact records into a recoverable rolling history.
How does JFR avoid recording every duration event?
Enabled settings and thresholds reject unneeded duration events before their details reach the recording path.
Why do application threads not share one hot JFR append cursor?
Each application thread appends into a private buffer and hands off the full buffer to shared memory.
How can periodic samples reveal hot code?
Repeated snapshots land most often in methods that occupy more execution time, so their profile counts grow larger.
Why do sample events carry stack IDs?
JFR stores repeated stack and method structures once, then lets many events refer to them with compact IDs.
How does a continuous recording keep recent history parseable?
Each chunk carries the metadata and checkpoints needed for parsing, while retention removes the oldest complete chunks.
Stage 1 of 7
Meet the pieces JFR moves
Java Flight Recorder watches a running JVM through small typed records. Let us meet the pieces before following their journey.
An event is one timestamped fact, such as a long lock wait, and its type tells tools which fields belong inside it.
A threshold is an early gate that rejects short duration events, which avoids paying to record details that the current configuration does not need.
A thread-local buffer is a private strip of memory. One Java thread appends event bytes using its own write cursor.
A stack sample is a periodic snapshot of where a thread is executing, so repeated snapshots can reveal which methods stay busy.
A reference pool stores repeated stacks and method metadata once. Those compact records will later need a durable container.
A chunk bundles the event stream with the information needed to parse it, while a rolling recording can discard older chunks as limits are reached.
These six pieces form one low-overhead path, so let us begin with the gate that decides whether an event enters at all.
Stage 2 of 7
Useful events pass a cheap gate
Now that we know the cast, we can follow three illustrative duration events. A setting decides whether each one is worth recording.
The enabled setting is the first check, because a disabled event type can stop before JFR gathers or writes its optional details.
The threshold then compares each event duration with the configured minimum, which keeps short events outside the buffer path.
The active threshold is 5 milliseconds, and the three events last 2, 9, and 28 milliseconds. Which records will cross the gate?
Which duration events pass the 5 ms threshold?
The accepted records move into a compact stream while the shorter record falls away, making the result of the gate visible before any buffer work begins.
Switch the Threshold control through every option. Compare which duration markers enter the recording and which disappear before buffer work.
Early filtering protects the rest of the recording path from avoidable work. Next, each accepted record needs a low-contention place to land.
Stage 3 of 7
Every thread writes in its own lane
The event gate produced compact records, and now each visible row belongs to one Java thread with its own empty memory strip and cursor.
One thread appends a record by advancing only its own cursor, so no unrelated thread needs to acquire that cursor first.
Several threads can append during the same beat because their cells are separate. Their cursors advance in parallel instead of taking turns.
One private strip is now full, but the application thread still has more events to record. What should happen to that completed strip?
What follows T0's full buffer?
The full strip detaches into global history while a fresh empty strip appears in its lane, so recording can continue after a brief handoff.
Switch the Write path control between Private lanes and Shared cursor. Compare independent appends with one queued cursor.
Private ownership keeps the common append path off busy application threads. Next, periodic stack samples will join the ordinary event stream.
Stage 4 of 7
Snapshots reveal the hot method
Private buffers can accept ordinary events, and profiling adds periodic snapshots that ask where a running thread is executing at selected moments.
The worked timeline contains a short parser, a long checkout loop, and a short response writer, with width representing time spent in each method.
Sample needles land at a fixed period. Each needle adds one stack reference instead of tracing every method entry and exit.
These snapshots capture only a few moments from the execution window. Which method should still lead after the sample hits are counted?
Which method should lead the sampled profile?
Most needles land inside the long checkout span, so its computed profile bar grows longest even though JFR never traced every call.
A few periodic stack snapshots make the repeatedly running method stand out without tracing every call.
Switch the Sample period control. Compare Checkout.run as a clear leader with the sparse three-way tie across methods.
Sampling trades complete tracing for bounded recurring work and a useful statistical signal for later diagnosis. Next, repeated stack details must become compact references.
Stage 5 of 7
Repeated stacks become small references
The sampler produced stack-bearing events, but many snapshots point to the same methods, so repeating every frame would waste recording space.
In repeated form, each event carries another visible tower of stack frames even when that tower matches one already recorded.
Several events carry the same stack shape, so storing every tower repeats identical structure. How should matching samples share that information?
How should matching stack samples reuse stored data?
The pool keeps each unique stack once and assigns it an ID, while sample events shrink into tokens that carry the matching reference.
The repeated towers collapse into one shared pool and a row of small IDs, so the saved structural units are visible beside the records.
Switch the Encoding control. Compare repeated frame units with the compact pool and its event references.
Reference pooling preserves stack identity while removing repeated structure from individual events throughout the recording. Next, those compact records need a self-contained file container.
Stage 6 of 7
Chunks preserve a rolling history
The compact records now need a durable container, so JFR writes a chunk with a header and an event stream that carries parsing information.
Metadata records describe event types and fields, while checkpoint records carry shared constants such as classes, methods, threads, and stack traces.
Ordinary events and execution samples share that stream. Their small references resolve through checkpoint data stored inside the same chunk.
A continuous recording keeps producing chunks after its history limit is full. Which files should remain when the newest chunk is sealed?
Which chunks remain after C5 fills a three-chunk window?
The newest sealed chunk joins the right edge while the oldest falls away, leaving a chronological window whose remaining chunks can still be parsed.
Adjust the Chunks kept control across its range. Compare whether trigger and pre-trigger context survive rotation for later diagnosis.
Chunk rotation bounds storage without breaking the parseability of retained history. Now let us connect every low-overhead decision in one final map.
Stage 7 of 7
The complete low-overhead path
We started at the event gate, where enabled settings and thresholds prevent unwanted duration events from entering the recording path.
We then gave every application thread a private buffer lane, so concurrent producers do not fight over one shared append cursor.
Periodic stack snapshots turned a computed execution timeline into a profile. The hottest method emerged without tracing every call.
Shared reference pools stored repeated stacks and methods once, while compact IDs carried those relationships into each matching event.
Filtering, private writes, sampling, and deduplication all reduce foreground work. Complete recording chunks preserve those compact observations after the moment passes.
Self-contained chunks connect every earlier piece into a rolling recording, and the worked numbers came from one deterministic model backed by official JFR specifications.
JFR remains practical in production because it filters early, writes privately, samples sparsely, reuses metadata, and rotates complete chunks.